From 40317a650d72c86e2529d5179cffe63399301de5 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 2 Aug 2017 09:12:38 -0400 Subject: [PATCH] Work on R4 for JPA server --- .../client/impl/RestfulClientFactory.java | 4 +- .../uhn/fhir/jpa/config/r4/BaseR4Config.java | 2 +- .../fhir/jpa/config/r4/WebsocketR4Config.java | 21 +- .../r4/WebsocketR4DispatcherConfig.java | 10 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 8 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 3 +- .../fhir/jpa/dao/r4/CustomObservationR4.java | 2 +- ...hirResourceDaoQuestionnaireResponseR4.java | 2 +- .../fhir/jpa/dao/r4/FhirResourceDaoR4.java | 2 +- .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 2 +- .../uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java | 2 +- .../jpa/dao/r4/JpaValidationSupportR4.java | 12 +- .../RestHookSubscriptionR4Interceptor.java | 5 +- .../WebSocketSubscriptionR4Interceptor.java | 107 + .../jpa/provider/r4/JpaSystemProviderR4.java | 2 +- .../r4/SubscriptionWebsocketHandlerR4.java | 348 ++ ...ptionWebsocketReturnResourceHandlerR4.java | 361 ++ .../fhir/jpa/term/HapiTerminologySvcR4.java | 7 +- .../config/TestDstu3WithoutLuceneConfig.java | 1 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 150 + .../jpa/config/TestR4WithoutLuceneConfig.java | 49 + .../java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java | 18 + .../fhir/jpa/dao/r4/BaseJpaR4SystemTest.java | 54 + .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 301 ++ .../r4/FhirResourceDaoCustomTypeR4Test.java | 43 + .../dao/r4/FhirResourceDaoDocumentR4Test.java | 31 + .../r4/FhirResourceDaoR4CodeSystemTest.java | 44 + .../r4/FhirResourceDaoR4ContainedTest.java | 61 + ...hirResourceDaoR4ExternalReferenceTest.java | 163 + .../r4/FhirResourceDaoR4InterceptorTest.java | 473 ++ ...ResourceDaoR4ReferentialIntegrityTest.java | 99 + ...ourceDaoR4SearchCustomSearchParamTest.java | 883 ++++ .../dao/r4/FhirResourceDaoR4SearchFtTest.java | 565 +++ .../r4/FhirResourceDaoR4SearchNoFtTest.java | 3440 ++++++++++++++ ...FhirResourceDaoR4SearchPageExpiryTest.java | 174 + ...urceDaoR4SearchWithLuceneDisabledTest.java | 225 + .../r4/FhirResourceDaoR4SubscriptionTest.java | 530 +++ .../r4/FhirResourceDaoR4TerminologyTest.java | 1208 +++++ .../jpa/dao/r4/FhirResourceDaoR4Test.java | 3588 ++++++++++++++ .../dao/r4/FhirResourceDaoR4UpdateTest.java | 868 ++++ .../dao/r4/FhirResourceDaoR4ValidateTest.java | 303 ++ .../dao/r4/FhirResourceDaoR4ValueSetTest.java | 242 + .../fhir/jpa/dao/r4/FhirSearchDaoR4Test.java | 184 + .../jpa/dao/r4/FhirSystemDaoR4SearchTest.java | 52 + .../fhir/jpa/dao/r4/FhirSystemDaoR4Test.java | 2763 +++++++++++ .../dao/r4/SearchParamExtractorR4Test.java | 73 + ...tionInterceptorResourceProviderR4Test.java | 302 ++ .../r4/BaseResourceProviderR4Test.java | 198 + .../uhn/fhir/jpa/provider/r4/CorsR4Test.java | 37 + .../provider/r4/PatientEverythingR4Test.java | 208 + .../r4/QuestionnaireResourceProviderR4.java | 13 + ...sourceProviderCustomSearchParamR4Test.java | 428 ++ .../r4/ResourceProviderInterceptorR4Test.java | 239 + ...ceProviderQuestionnaireResponseR4Test.java | 235 + .../r4/ResourceProviderR4BundleTest.java | 46 + .../r4/ResourceProviderR4CodeSystemTest.java | 264 + .../provider/r4/ResourceProviderR4Test.java | 4225 +++++++++++++++++ .../r4/ResourceProviderR4ValueSetTest.java | 533 +++ .../fhir/jpa/provider/r4/ServerR4Test.java | 68 + .../r4/StaleSearchDeletingSvcR4Test.java | 97 + .../jpa/provider/r4/SubscriptionsR4Test.java | 515 ++ .../jpa/provider/r4/SystemProviderR4Test.java | 662 +++ ...SystemProviderTransactionSearchR4Test.java | 312 ++ .../r4/TerminologyUploaderProviderR4Test.java | 209 + .../r4/PagingMultinodeProviderR4Test.java | 97 + .../fhir/jpa/subscription/r4/FhirR4Util.java | 94 + .../FhirSubscriptionWithCriteriaR4Test.java | 171 + ...rSubscriptionWithSubscriptionIdR4Test.java | 169 + .../subscription/r4/RestHookTestR4Test.java | 362 ++ ...nterceptorRegisteredToDaoConfigR4Test.java | 299 ++ .../fhir/r4/elementmodel/ObjectConverter.java | 21 + .../ServerCapabilityStatementProvider.java | 2 +- .../hl7/fhir/r4/model/BaseDateTimeType.java | 6 +- .../org/hl7/fhir/r4/model/DateTimeType.java | 5 +- .../java/org/hl7/fhir/r4/model/DateType.java | 8 +- .../org/hl7/fhir/r4/model/InstantType.java | 5 +- .../java/org/hl7/fhir/r4/model/Period.java | 14 +- .../fhir/r4/model/TemporalPrecisionEnum.java | 62 - .../terminologies/ValueSetExpanderSimple.java | 2 +- .../org/hl7/fhir/r4/utils/FHIRPathEngine.java | 41 +- .../ca/uhn/fhir/rest/client/ClientR4Test.java | 16 +- .../ClientServerValidationDstu1Test.java | 20 +- .../fhir/rest/client/GenericClientTest.java | 7 - .../rest/client/LoggingInterceptorTest.java | 4 +- .../rest/client/SearchClientDstu1Test.java | 130 - .../fhir/rest/client/SearchClientTest.java | 140 + .../uhn/fhir/rest/client/SortClientTest.java | 2 +- .../ca/uhn/fhir/rest/server/IncludeTest.java | 6 +- .../fhir/r4/elementmodel/PropertyTest.java | 2 +- .../customPatientSd.xml | 0 .../test/resources/logback-test-dstuforce.xml | 0 .../{resource => resources}/logback-test.xml | 0 .../fhir/r4/validation/InstanceValidator.java | 25 +- .../fhir/r4/validation/ValidationEngine.java | 21 - .../FhirInstanceValidatorR4Test.java | 23 +- .../resources/vm/jpa_spring_beans_java.vm | 4 + 96 files changed, 27458 insertions(+), 376 deletions(-) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/{ => r4}/RestHookSubscriptionR4Interceptor.java (98%) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/WebSocketSubscriptionR4Interceptor.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4WithoutLuceneConfig.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4SystemTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCustomTypeR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4SearchTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CorsR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/QuestionnaireResourceProviderR4.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderQuestionnaireResponseR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java delete mode 100644 hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/TemporalPrecisionEnum.java delete mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientDstu1Test.java create mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientTest.java rename hapi-fhir-structures-r4/src/test/{resource => resources}/customPatientSd.xml (100%) rename {hapi-fhir-structures-dstu => hapi-fhir-structures-r4}/src/test/resources/logback-test-dstuforce.xml (100%) rename hapi-fhir-structures-r4/src/test/{resource => resources}/logback-test.xml (100%) delete mode 100644 hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/ValidationEngine.java diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/RestfulClientFactory.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/RestfulClientFactory.java index 28b32701f66..298eda9787d 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/RestfulClientFactory.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/RestfulClientFactory.java @@ -334,8 +334,10 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory { } else { if (serverFhirVersionString.startsWith("0.4") || serverFhirVersionString.startsWith("0.5") || serverFhirVersionString.startsWith("1.0.")) { serverFhirVersionEnum = FhirVersionEnum.DSTU2; - } else if (serverFhirVersionString.startsWith("3.")) { + } else if (serverFhirVersionString.startsWith("3.0.")) { serverFhirVersionEnum = FhirVersionEnum.DSTU3; + } else if (serverFhirVersionString.startsWith("3.1.")) { + serverFhirVersionEnum = FhirVersionEnum.R4; } else { // we'll be lenient and accept this ourLog.debug("Server conformance statement indicates unknown FHIR version: {}", serverFhirVersionString); 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 dfdbf518ca1..820de327f69 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 @@ -34,7 +34,7 @@ import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4; import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; -import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionR4Interceptor; +import ca.uhn.fhir.jpa.interceptor.r4.RestHookSubscriptionR4Interceptor; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; import ca.uhn.fhir.jpa.term.*; import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java index b2ea4e2a58b..fd5605e29ef 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java @@ -23,18 +23,15 @@ package ca.uhn.fhir.jpa.config.r4; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Controller; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.config.annotation.*; import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; -import ca.uhn.fhir.jpa.interceptor.WebSocketSubscriptionDstu3Interceptor; -import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu3; +import ca.uhn.fhir.jpa.interceptor.r4.WebSocketSubscriptionR4Interceptor; +import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @Configuration @@ -44,17 +41,17 @@ public class WebsocketR4Config implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) { - theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu3").setAllowedOrigins("*"); + theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/r4").setAllowedOrigins("*"); } @Bean(autowire = Autowire.BY_TYPE) public WebSocketHandler subscriptionWebSocketHandler() { - PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu3.class); + PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerR4.class); return retVal; } @Bean(destroyMethod="destroy") - public TaskScheduler websocketTaskSchedulerDstu3() { + public TaskScheduler websocketTaskSchedulerR4() { final ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler() { private static final long serialVersionUID = 1L; @@ -65,15 +62,15 @@ public class WebsocketR4Config implements WebSocketConfigurer { getScheduledThreadPoolExecutor().setContinueExistingPeriodicTasksAfterShutdownPolicy(false); } }; - retVal.setThreadNamePrefix("ws-dstu3-"); + retVal.setThreadNamePrefix("ws-r4-"); retVal.setPoolSize(5); return retVal; } @Bean - public IServerInterceptor webSocketSubscriptionDstu3Interceptor(){ - return new WebSocketSubscriptionDstu3Interceptor(); + public IServerInterceptor webSocketSubscriptionR4Interceptor(){ + return new WebSocketSubscriptionR4Interceptor(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java index 63131405f72..0913f90b2da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java @@ -22,14 +22,14 @@ package ca.uhn.fhir.jpa.config.r4; import javax.annotation.PostConstruct; -import org.hl7.fhir.dstu3.model.Subscription; +import org.hl7.fhir.r4.model.Subscription; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; -import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu3; +import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4; @Configuration public class WebsocketR4DispatcherConfig { @@ -38,12 +38,12 @@ public class WebsocketR4DispatcherConfig { private FhirContext myCtx; @Autowired - private IFhirResourceDao mySubscriptionDao; + private IFhirResourceDao mySubscriptionDao; @PostConstruct public void postConstruct() { - SubscriptionWebsocketHandlerDstu3.setCtx(myCtx); - SubscriptionWebsocketHandlerDstu3.setSubscriptionDao((IFhirResourceDaoSubscription) mySubscriptionDao); + SubscriptionWebsocketHandlerR4.setCtx(myCtx); + SubscriptionWebsocketHandlerR4.setSubscriptionDao((IFhirResourceDaoSubscription) mySubscriptionDao); } } 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 ceac1fbc24b..7cbd323be92 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 @@ -34,9 +34,9 @@ import javax.xml.stream.events.XMLEvent; import org.apache.commons.lang3.*; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.r4.model.BaseResource; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; @@ -779,7 +779,7 @@ public abstract class BaseHapiFhirDao implements IDao { List childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class); for (@SuppressWarnings("rawtypes") IPrimitiveType nextType : childElements) { - if (nextType instanceof StringDt || nextType.getClass().equals(StringType.class)) { + if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) { String nextValue = nextType.getValueAsString(); if (isNotBlank(nextValue)) { retVal.append(nextValue.replace("\n", " ").replace("\r", " ")); @@ -804,7 +804,7 @@ public abstract class BaseHapiFhirDao implements IDao { private void populateResourceIdFromEntity(BaseHasResource theEntity, final IBaseResource theResource) { IIdType id = theEntity.getIdDt(); if (getContext().getVersion().getVersion().isRi()) { - id = new IdType(id.getValue()); + id = getContext().getVersion().newIdType().setValue(id.getValue()); } theResource.setId(id); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index c2ca9a5d2c4..54ebd6dc0c1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -29,7 +29,6 @@ import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.instance.model.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; @@ -990,7 +989,7 @@ public abstract class BaseHapiFhirResourceDao extends B IIdType id = theEntity.getIdDt(); if (getContext().getVersion().getVersion().isRi()) { - id = new IdType(id.getValue()); + id = getContext().getVersion().newIdType().setValue(id.getValue()); } outcome.setId(id); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/CustomObservationR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/CustomObservationR4.java index 97b6011a250..971c9c356f3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/CustomObservationR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/CustomObservationR4.java @@ -30,7 +30,7 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef; @ResourceDef(name = "Observation", profile = CustomObservationR4.PROFILE) public class CustomObservationR4 extends Observation { - public static final String PROFILE = "http://custom_ObservationDstu3"; + public static final String PROFILE = "http://custom_ObservationR4"; private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoQuestionnaireResponseR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoQuestionnaireResponseR4.java index 2828666729f..ca6eaa64655 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoQuestionnaireResponseR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoQuestionnaireResponseR4.java @@ -40,7 +40,7 @@ public class FhirResourceDaoQuestionnaireResponseR4 extends FhirResourceDaoR4 extends BaseHapiFhirResou private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4.class); @Autowired() - @Qualifier("myInstanceValidatorDstu3") + @Qualifier("myInstanceValidatorR4") private IValidatorModule myInstanceValidator; @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index 61c2d6ab803..0f8021b80f1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -49,7 +49,7 @@ import ca.uhn.fhir.util.ElementUtil; public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 implements IFhirResourceDaoValueSet { @Autowired - @Qualifier("myJpaValidationSupportChainDstu3") + @Qualifier("myJpaValidationSupportChainR4") private IValidationSupport myValidationSupport; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java index 8c51cd6b050..78f45282a45 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java @@ -194,7 +194,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { } /* - * See FhirSystemDaoDstu3Test#testTransactionWithPlaceholderIdInMatchUrl + * See FhirSystemDaoR4Test#testTransactionWithPlaceholderIdInMatchUrl * Basically if the resource has a match URL that references a placeholder, * we try to handle the resource with the placeholder first. */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java index 14c24366140..dd7e81ef0c1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java @@ -48,23 +48,23 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4 { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaValidationSupportR4.class); @Autowired - @Qualifier("myStructureDefinitionDaoDstu3") + @Qualifier("myStructureDefinitionDaoR4") private IFhirResourceDao myStructureDefinitionDao; @Autowired - @Qualifier("myValueSetDaoDstu3") + @Qualifier("myValueSetDaoR4") private IFhirResourceDao myValueSetDao; @Autowired - @Qualifier("myQuestionnaireDaoDstu3") + @Qualifier("myQuestionnaireDaoR4") private IFhirResourceDao myQuestionnaireDao; @Autowired - @Qualifier("myCodeSystemDaoDstu3") + @Qualifier("myCodeSystemDaoR4") private IFhirResourceDao myCodeSystemDao; @Autowired - private FhirContext myDstu3Ctx; + private FhirContext myR4Ctx; public JpaValidationSupportR4() { super(); @@ -91,7 +91,7 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4 { localReference = true; } - String resourceName = myDstu3Ctx.getResourceDefinition(theClass).getName(); + String resourceName = myR4Ctx.getResourceDefinition(theClass).getName(); IBundleProvider search; if ("ValueSet".equals(resourceName)) { if (localReference) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionR4Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/RestHookSubscriptionR4Interceptor.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionR4Interceptor.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/RestHookSubscriptionR4Interceptor.java index aab5f21aead..15ea72799bf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionR4Interceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/RestHookSubscriptionR4Interceptor.java @@ -1,5 +1,5 @@ -package ca.uhn.fhir.jpa.interceptor; +package ca.uhn.fhir.jpa.interceptor.r4; /*- * #%L @@ -43,6 +43,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.interceptor.BaseRestHookSubscriptionInterceptor; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.thread.HttpRequestR4Job; import ca.uhn.fhir.rest.api.*; @@ -63,7 +64,7 @@ public class RestHookSubscriptionR4Interceptor extends BaseRestHookSubscriptionI private final List myRestHookSubscriptions = new ArrayList(); @Autowired - @Qualifier("mySubscriptionDaoDstu3") + @Qualifier("mySubscriptionDaoR4") private IFhirResourceDao mySubscriptionDao; private boolean notifyOnDelete = false; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/WebSocketSubscriptionR4Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/WebSocketSubscriptionR4Interceptor.java new file mode 100644 index 00000000000..d29bb817347 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/r4/WebSocketSubscriptionR4Interceptor.java @@ -0,0 +1,107 @@ + +package ca.uhn.fhir.jpa.interceptor.r4; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.hl7.fhir.r4.model.Subscription; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 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.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; +import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; + +public class WebSocketSubscriptionR4Interceptor extends ServerOperationInterceptorAdapter { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebSocketSubscriptionR4Interceptor.class); + + private IFhirResourceDaoSubscription mySubscriptionDaoCasted; + + @Autowired + @Qualifier("mySubscriptionDaoR4") + private IFhirResourceDao mySubscriptionDao; + + @Override + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + if (theRequestDetails.getRestOperationType().equals(RestOperationTypeEnum.DELETE)) { + mySubscriptionDaoCasted.pollForNewUndeliveredResources(theRequestDetails.getResourceName()); + } + + return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse); + } + + /** + * Checks for websocket subscriptions + * + * @param theRequestDetails + * A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the {@link HttpServletRequest servlet request}. + * @param theResponseObject + * The actual object which is being streamed to the client as a response + * @return + */ + @Override + public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) { + if (theRequestDetails.getResourceName() == null || + theRequestDetails.getResourceName().isEmpty() || + theRequestDetails.getResourceName().equals("Subscription")) { + return super.outgoingResponse(theRequestDetails, theResponseObject); + } + + if (theRequestDetails.getRequestType().equals(RequestTypeEnum.POST) || theRequestDetails.getRequestType().equals(RequestTypeEnum.PUT)) { + ourLog.info("Found POST or PUT for a non-subscription resource"); + mySubscriptionDaoCasted.pollForNewUndeliveredResources(theRequestDetails.getResourceName()); + } + + return super.outgoingResponse(theRequestDetails, theResponseObject); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @PostConstruct + public void postConstruct() { + mySubscriptionDaoCasted = (IFhirResourceDaoSubscription) mySubscriptionDao; + } + + @Override + public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { + // nothing + } + + @Override + public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) { + // nothing + } + + @Override + public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { + // nothing + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java index a5d66991781..83027e4dbfa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java @@ -42,7 +42,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus { @Autowired() - @Qualifier("mySystemDaoDstu3") + @Qualifier("mySystemDaoR4") private IFhirSystemDao mySystemDao; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java new file mode 100644 index 00000000000..d33fb54a66c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java @@ -0,0 +1,348 @@ +package ca.uhn.fhir.jpa.subscription.r4; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 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.io.IOException; +import java.util.List; +import java.util.concurrent.ScheduledFuture; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.hl7.fhir.r4.model.IdType; +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.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; + +public class SubscriptionWebsocketHandlerR4 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable { + private static FhirContext ourCtx; + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandlerR4.class); + + private static IFhirResourceDaoSubscription ourSubscriptionDao; + + private ScheduledFuture myScheduleFuture; + + private IState myState = new InitialState(); + + private IIdType mySubscriptionId; + + private Long mySubscriptionPid; + + @Autowired + @Qualifier("websocketTaskSchedulerR4") + private TaskScheduler myTaskScheduler; + @Override + public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception { + super.afterConnectionClosed(theSession, theStatus); + ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress()); + } + + @Override + public void afterConnectionEstablished(WebSocketSession theSession) throws Exception { + super.afterConnectionEstablished(theSession); + ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress()); + } + + protected void handleFailure(Exception theE) { + ourLog.error("Failure during communication", theE); + } + + @Override + protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception { + ourLog.info("Textmessage: " + theMessage.getPayload()); + + myState.handleTextMessage(theSession, theMessage); + } + + @Override + public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception { + super.handleTransportError(theSession, theException); + ourLog.error("Transport error", theException); + } + + @PostConstruct + public void postConstruct() { + ourLog.info("Creating scheduled task for subscription websocket connection"); + myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000); + } + + @PreDestroy + public void preDescroy() { + ourLog.info("Cancelling scheduled task for subscription websocket connection"); + myScheduleFuture.cancel(true); + IState state = myState; + if (state != null) { + state.closing(); + } + } + + @Override + public void run() { + Long subscriptionPid = mySubscriptionPid; + if (subscriptionPid == null) { + return; + } + + ourLog.debug("Subscription {} websocket handler polling", subscriptionPid); + + List results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid); + if (results.isEmpty() == false) { + myState.deliver(results); + } + } + + public static void setCtx(FhirContext theCtx) { + ourCtx = theCtx; + } + + public static void setSubscriptionDao(IFhirResourceDaoSubscription theSubscriptionDao) { + ourSubscriptionDao = theSubscriptionDao; + } + + private class BoundDynamicSubscriptionState implements IState { + + private EncodingEnum myEncoding; + private WebSocketSession mySession; + + public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) { + mySession = theSession; + myEncoding = theEncoding; + } + + @Override + public void closing() { + ourLog.info("Deleting subscription {}", mySubscriptionId); + try { + ourSubscriptionDao.delete(mySubscriptionId, null); + } catch (Exception e) { + handleFailure(e); + } + } + + @Override + public void deliver(List theResults) { + try { + for (IBaseResource nextResource : theResults) { + ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement()); + String encoded = myEncoding.newParser(ourCtx).encodeResourceToString(nextResource); + String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded; + mySession.sendMessage(new TextMessage(payload)); + } + } catch (IOException e) { + handleFailure(e); + } + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + try { + theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); + } catch (IOException e) { + handleFailure(e); + } + } + + } + + private class BoundStaticSubscipriptionState implements IState { + + private WebSocketSession mySession; + + public BoundStaticSubscipriptionState(WebSocketSession theSession) { + mySession = theSession; + } + + @Override + public void closing() { + // nothing + } + + @Override + public void deliver(List theResults) { + try { + String payload = "ping " + mySubscriptionId.getIdPart(); + ourLog.info("Sending WebSocket message: {}", payload); + mySession.sendMessage(new TextMessage(payload)); + } catch (IOException e) { + handleFailure(e); + } + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + try { + theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); + } catch (IOException e) { + handleFailure(e); + } + } + + } + + private class InitialState implements IState { + + private IIdType bindSimple(WebSocketSession theSession, String theBindString) { + IdType id = new IdType(theBindString); + + if (!id.hasIdPart() || !id.isIdPartValid()) { + try { + String message = "Invalid bind request - No ID included"; + ourLog.warn(message); + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message)); + } catch (IOException e) { + handleFailure(e); + } + return null; + } + + if (id.hasResourceType() == false) { + id = id.withResourceType("Subscription"); + } + + try { + Subscription subscription = ourSubscriptionDao.read(id, null); + mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); + mySubscriptionId = subscription.getIdElement(); + myState = new BoundStaticSubscipriptionState(theSession); + } catch (ResourceNotFoundException e) { + try { + String message = "Invalid bind request - Unknown subscription: " + id.getValue(); + ourLog.warn(message); + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message)); + } catch (IOException e1) { + handleFailure(e); + } + return null; + } + + return id; + } + + private IIdType bingSearch(WebSocketSession theSession, String theRemaining) { + Subscription subscription = new Subscription(); + subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subscription.setStatus(SubscriptionStatus.ACTIVE); + subscription.setCriteria(theRemaining); + + try { + String params = theRemaining.substring(theRemaining.indexOf('?')+1); + List paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&'); + EncodingEnum encoding = EncodingEnum.JSON; + for (NameValuePair nameValuePair : paramValues) { + if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) { + EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue()); + if (nextEncoding != null) { + encoding = nextEncoding; + } + } + } + + IIdType id = ourSubscriptionDao.create(subscription).getId(); + + mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); + mySubscriptionId = subscription.getIdElement(); + myState = new BoundDynamicSubscriptionState(theSession, encoding); + + return id; + } catch (UnprocessableEntityException e) { + ourLog.warn("Failed to bind subscription: " + e.getMessage()); + try { + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage())); + } catch (IOException e2) { + handleFailure(e2); + } + } catch (Exception e) { + handleFailure(e); + try { + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included")); + } catch (IOException e2) { + handleFailure(e2); + } + } + return null; + } + + @Override + public void closing() { + // nothing + } + + @Override + public void deliver(List theResults) { + throw new IllegalStateException(); + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + String message = theMessage.getPayload(); + if (message.startsWith("bind ")) { + String remaining = message.substring("bind ".length()); + + IIdType subscriptionId; + if (remaining.contains("?")) { + subscriptionId = bingSearch(theSession, remaining); + } else { + subscriptionId = bindSimple(theSession, remaining); + if (subscriptionId == null) { + return; + } + } + + try { + theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart())); + } catch (IOException e) { + handleFailure(e); + } + + } + } + + } + + private interface IState { + + void closing(); + + void deliver(List theResults); + + void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java new file mode 100644 index 00000000000..d147d004fae --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java @@ -0,0 +1,361 @@ + +package ca.uhn.fhir.jpa.subscription.r4; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ScheduledFuture; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.hl7.fhir.r4.model.IdType; +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.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; + +public class SubscriptionWebsocketReturnResourceHandlerR4 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerR4.class); + + @Autowired + private FhirContext myCtx; + + private ScheduledFuture myScheduleFuture; + + private IState myState = new InitialState(); + + @Autowired + private IFhirResourceDaoSubscription mySubscriptionDao; + + private IIdType mySubscriptionId; + private Long mySubscriptionPid; + + @Autowired + @Qualifier("websocketTaskScheduler") + private TaskScheduler myTaskScheduler; + + @Override + public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception { + super.afterConnectionClosed(theSession, theStatus); + ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress()); + } + + @Override + public void afterConnectionEstablished(WebSocketSession theSession) throws Exception { + super.afterConnectionEstablished(theSession); + ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress()); + } + + protected void handleFailure(Exception theE) { + ourLog.error("Failure during communication", theE); + } + + @Override + protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception { + ourLog.info("Textmessage: " + theMessage.getPayload()); + + myState.handleTextMessage(theSession, theMessage); + } + + @Override + public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception { + super.handleTransportError(theSession, theException); + ourLog.error("Transport error", theException); + } + + @PostConstruct + public void postConstruct() { + ourLog.info("Creating scheduled task for subscription websocket connection"); + myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000); + } + + @PreDestroy + public void preDescroy() { + ourLog.info("Cancelling scheduled task for subscription websocket connection"); + myScheduleFuture.cancel(true); + IState state = myState; + if (state != null) { + state.closing(); + } + } + + @Override + public void run() { + Long subscriptionPid = mySubscriptionPid; + if (subscriptionPid == null) { + return; + } + + ourLog.debug("Subscription {} websocket handler polling", subscriptionPid); + + List results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid); + if (results.isEmpty() == false) { + myState.deliver(results); + } + } + + private class BoundDynamicSubscriptionState implements IState { + + private EncodingEnum myEncoding; + private WebSocketSession mySession; + + public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) { + mySession = theSession; + myEncoding = theEncoding; + } + + @Override + public void closing() { + ourLog.info("Deleting subscription {}", mySubscriptionId); + try { + mySubscriptionDao.delete(mySubscriptionId, null); + } catch (Exception e) { + handleFailure(e); + } + } + + @Override + public void deliver(List theResults) { + try { + for (IBaseResource nextResource : theResults) { + ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement()); + String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource); + String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded; + mySession.sendMessage(new TextMessage(payload)); + } + } catch (IOException e) { + handleFailure(e); + } + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + try { + theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); + } catch (IOException e) { + handleFailure(e); + } + } + + } + + private class BoundStaticSubscriptionState implements IState { + + private EncodingEnum myEncoding; + private WebSocketSession mySession; + + public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) { + mySession = theSession; + myEncoding = theEncoding; + } + + @Override + public void closing() { + // nothing + } + + @Override + public void deliver(List theResults) { + try { + for (IBaseResource nextResource : theResults) { + ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement()); + String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource); + String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded; + mySession.sendMessage(new TextMessage(payload)); + } + } catch (IOException e) { + handleFailure(e); + } + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + try { + theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); + } catch (IOException e) { + handleFailure(e); + } + } + + } + + private class InitialState implements IState { + + private IIdType bindSimple(WebSocketSession theSession, String theBindString) { + IdType id = new IdType(theBindString); + + if (!id.hasIdPart() || !id.isIdPartValid()) { + try { + String message = "Invalid bind request - No ID included"; + ourLog.warn(message); + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message)); + } catch (IOException e) { + handleFailure(e); + } + return null; + } + + if (id.hasResourceType() == false) { + id = id.withResourceType("Subscription"); + } + + try { + Subscription subscription = mySubscriptionDao.read(id, null); + EncodingEnum encoding = EncodingEnum.JSON; + String criteria = subscription.getCriteria(); + String params = criteria.substring(criteria.indexOf('?') + 1); + List paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&'); + for (NameValuePair nameValuePair : paramValues) { + if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) { + EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue()); + if (nextEncoding != null) { + encoding = nextEncoding; + } + } + } + + mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); + mySubscriptionId = subscription.getIdElement(); + myState = new BoundStaticSubscriptionState(theSession, encoding); + } catch (ResourceNotFoundException e) { + try { + String message = "Invalid bind request - Unknown subscription: " + id.getValue(); + ourLog.warn(message); + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message)); + } catch (IOException e1) { + handleFailure(e); + } + return null; + } + + return id; + } + + private IIdType bindSearch(WebSocketSession theSession, String theRemaining) { + Subscription subscription = new Subscription(); + subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subscription.setStatus(SubscriptionStatus.ACTIVE); + subscription.setCriteria(theRemaining); + + try { + String params = theRemaining.substring(theRemaining.indexOf('?')+1); + List paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&'); + EncodingEnum encoding = EncodingEnum.JSON; + for (NameValuePair nameValuePair : paramValues) { + if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) { + EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue()); + if (nextEncoding != null) { + encoding = nextEncoding; + } + } + } + + IIdType id = mySubscriptionDao.create(subscription).getId(); + + mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); + mySubscriptionId = subscription.getIdElement(); + myState = new BoundDynamicSubscriptionState(theSession, encoding); + + return id; + } catch (UnprocessableEntityException e) { + ourLog.warn("Failed to bind subscription: " + e.getMessage()); + try { + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage())); + } catch (IOException e2) { + handleFailure(e2); + } + } catch (Exception e) { + handleFailure(e); + try { + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included")); + } catch (IOException e2) { + handleFailure(e2); + } + } + return null; + } + + @Override + public void closing() { + // nothing + } + + @Override + public void deliver(List theResults) { + throw new IllegalStateException(); + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + String message = theMessage.getPayload(); + if (message.startsWith("bind ")) { + String remaining = message.substring("bind ".length()); + + IIdType subscriptionId; + if (remaining.contains("?")) { + subscriptionId = bindSearch(theSession, remaining); + } else { + subscriptionId = bindSimple(theSession, remaining); + if (subscriptionId == null) { + return; + } + } + + try { + theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart())); + } catch (IOException e) { + handleFailure(e); + } + + } + } + + } + + private interface IState { + + void closing(); + + void deliver(List theResults); + + void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index 2950eacfdb5..990d7d60750 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -40,12 +40,12 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -57,7 +57,8 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcR4.class); @Autowired - private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; + @Qualifier("myCodeSystemDaoR4") + private IFhirResourceDao myCodeSystemResourceDao; @Autowired private IValidationSupport myValidationSupport; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java index d0680df9c8d..9a97d246040 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java @@ -19,6 +19,7 @@ public class TestDstu3WithoutLuceneConfig extends TestDstu3Config { /** * Disable fulltext searching */ + @Override public IFulltextSearchSvc searchDaoDstu3() { return null; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java new file mode 100644 index 00000000000..3a3c6eed2e9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -0,0 +1,150 @@ +package ca.uhn.fhir.jpa.config; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.BasicDataSource; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.context.annotation.*; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.validation.ResultSeverityEnum; +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; + +@Configuration +@EnableTransactionManagement() +public class TestR4Config extends BaseJavaConfigR4 { + + static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestR4Config.class); + + @Bean() + public DaoConfig daoConfig() { + return new DaoConfig(); + } + + private Exception myLastStackTrace; + + @Bean() + public DataSource dataSource() { + BasicDataSource retVal = new BasicDataSource() { + + + @Override + public Connection getConnection() throws SQLException { + ConnectionWrapper retVal; + try { + retVal = new ConnectionWrapper(super.getConnection()); + } catch (Exception e) { + ourLog.error("Exceeded maximum wait for connection", e); + logGetConnectionStackTrace(); + System.exit(1); + retVal = null; + } + + try { + throw new Exception(); + } catch (Exception e) { + myLastStackTrace = e; + } + + return retVal; + } + + private void logGetConnectionStackTrace() { + StringBuilder b = new StringBuilder(); + b.append("Last connection request stack trace:"); + for (StackTraceElement next : myLastStackTrace.getStackTrace()) { + b.append("\n "); + b.append(next.getClassName()); + b.append("."); + b.append(next.getMethodName()); + b.append("("); + b.append(next.getFileName()); + b.append(":"); + b.append(next.getLineNumber()); + b.append(")"); + } + ourLog.info(b.toString()); + } + + }; + retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); + retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true"); + retVal.setMaxWaitMillis(10000); + retVal.setUsername(""); + retVal.setPassword(""); + + /* + * We use a randomized number of maximum threads in order to try + * and catch any potential deadlocks caused by database connection + * starvation + */ + int maxThreads = (int) (Math.random() * 6) + 1; + retVal.setMaxTotal(maxThreads); + + DataSource dataSource = ProxyDataSourceBuilder + .create(retVal) + // .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") + .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) + .countQuery() + .build(); + + return dataSource; + } + + @Bean() + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean(); + retVal.setPersistenceUnitName("PU_HapiFhirJpaR4"); + retVal.setDataSource(dataSource()); + retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity"); + retVal.setPersistenceProvider(new HibernatePersistenceProvider()); + retVal.setJpaProperties(jpaProperties()); + return retVal; + } + + private Properties jpaProperties() { + Properties extraProperties = new Properties(); + extraProperties.put("hibernate.jdbc.batch_size", "50"); + extraProperties.put("hibernate.format_sql", "false"); + extraProperties.put("hibernate.show_sql", "false"); + extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.default.directory_provider", "ram"); + 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(instanceValidatorR4()); + + return requestValidator; + } + + @Bean() + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4WithoutLuceneConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4WithoutLuceneConfig.java new file mode 100644 index 00000000000..a01aa1ec80d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4WithoutLuceneConfig.java @@ -0,0 +1,49 @@ +package ca.uhn.fhir.jpa.config; + +import java.util.Properties; + +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; + +@Configuration +@EnableTransactionManagement() +public class TestR4WithoutLuceneConfig extends TestR4Config { + + /** + * Disable fulltext searching + */ + @Override + public IFulltextSearchSvc searchDaoR4() { + return null; + } + + @Override + @Bean() + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean(); + retVal.setPersistenceUnitName("PU_HapiFhirJpaR4"); + retVal.setDataSource(dataSource()); + retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity"); + retVal.setPersistenceProvider(new HibernatePersistenceProvider()); + retVal.setJpaProperties(jpaProperties()); + return retVal; + } + + private 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", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.autoregister_listeners", "false"); + return extraProperties; + } + +} 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 6e3e786e1d1..a374f9738f9 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 @@ -67,6 +67,14 @@ public abstract class BaseJpaTest { return bundle; } + protected org.hl7.fhir.r4.model.Bundle toBundleR4(IBundleProvider theSearch) { + org.hl7.fhir.r4.model.Bundle bundle = new org.hl7.fhir.r4.model.Bundle(); + for (IBaseResource next : theSearch.getResources(0, theSearch.size())) { + bundle.addEntry().setResource((org.hl7.fhir.r4.model.Resource) next); + } + return bundle; + } + protected abstract FhirContext getContext(); protected List toUnqualifiedVersionlessIdValues(IBaseBundle theFound) { @@ -132,6 +140,16 @@ public abstract class BaseJpaTest { return retVal; } + protected List toUnqualifiedVersionlessIds(org.hl7.fhir.r4.model.Bundle theFound) { + List retVal = new ArrayList(); + for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next : theFound.getEntry()) { + // if (next.getResource()!= null) { + retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless()); + // } + } + return retVal; + } + protected List toUnqualifiedVersionlessIdValues(IBundleProvider theFound) { List retVal = new ArrayList(); int size = theFound.size(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4SystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4SystemTest.java new file mode 100644 index 00000000000..f70ad99161f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4SystemTest.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.junit.AfterClass; +import org.junit.Before; + +import ca.uhn.fhir.jpa.rp.r4.PatientResourceProvider; +import ca.uhn.fhir.rest.api.server.IRequestOperationCallback; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +public abstract class BaseJpaR4SystemTest extends BaseJpaR4Test { + protected ServletRequestDetails mySrd; + private RestfulServer myServer; + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @SuppressWarnings("unchecked") + @Before + public void before() throws ServletException { + mySrd = mock(ServletRequestDetails.class); + when(mySrd.getRequestOperationCallback()).thenReturn(mock(IRequestOperationCallback.class)); + + if (myServer == null) { + myServer = new RestfulServer(myFhirCtx); + + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + myServer.setResourceProviders(patientRp); + myServer.init(mock(ServletConfig.class)); + } + + when(mySrd.getServer()).thenReturn(myServer); + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + when(mySrd.getServletRequest()).thenReturn(servletRequest); + when(mySrd.getFhirServerBase()).thenReturn("http://example.com/base"); + when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class)); + when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("/Patient")); + } + +} 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 new file mode 100644 index 00000000000..07fa7bc6134 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -0,0 +1,301 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import javax.persistence.EntityManager; + +import org.apache.commons.io.IOUtils; +import org.hibernate.search.jpa.FullTextEntityManager; +import org.hibernate.search.jpa.Search; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.*; +import org.junit.*; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestR4Config; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; +import ca.uhn.fhir.jpa.search.*; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; +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.interceptor.IServerInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; + +//@formatter:off +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes= {TestR4Config.class}) +//@formatter:on +public abstract class BaseJpaR4Test extends BaseJpaTest { + + private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4; + private static IFhirResourceDaoValueSet ourValueSetDao; + + // @Autowired + // protected HapiWorkerContext myHapiWorkerContext; + @Autowired + @Qualifier("myAllergyIntoleranceDaoR4") + protected IFhirResourceDao myAllergyIntoleranceDao; + @Autowired + protected ApplicationContext myAppCtx; + @Autowired + @Qualifier("myAppointmentDaoR4") + protected IFhirResourceDao myAppointmentDao; + @Autowired + @Qualifier("myAuditEventDaoR4") + protected IFhirResourceDao myAuditEventDao; + @Autowired + @Qualifier("myBundleDaoR4") + protected IFhirResourceDao myBundleDao; + @Autowired + @Qualifier("myCarePlanDaoR4") + protected IFhirResourceDao myCarePlanDao; + @Autowired + @Qualifier("myCodeSystemDaoR4") + protected IFhirResourceDaoCodeSystem myCodeSystemDao; + @Autowired + @Qualifier("myCompartmentDefinitionDaoR4") + protected IFhirResourceDao myCompartmentDefinitionDao; + @Autowired + @Qualifier("myConceptMapDaoR4") + protected IFhirResourceDao myConceptMapDao; + @Autowired + @Qualifier("myConditionDaoR4") + protected IFhirResourceDao myConditionDao; + @Autowired + protected DaoConfig myDaoConfig; + @Autowired + @Qualifier("myDeviceDaoR4") + protected IFhirResourceDao myDeviceDao; + @Autowired + @Qualifier("myDiagnosticReportDaoR4") + protected IFhirResourceDao myDiagnosticReportDao; + @Autowired + @Qualifier("myEncounterDaoR4") + protected IFhirResourceDao myEncounterDao; + // @PersistenceContext() + @Autowired + protected EntityManager myEntityManager; + @Autowired + protected FhirContext myFhirCtx; + @Autowired + @Qualifier("myGroupDaoR4") + protected IFhirResourceDao myGroupDao; + @Autowired + @Qualifier("myImmunizationDaoR4") + protected IFhirResourceDao myImmunizationDao; + @Autowired + @Qualifier("myImmunizationRecommendationDaoR4") + protected IFhirResourceDao myImmunizationRecommendationDao; + protected IServerInterceptor myInterceptor; + @Autowired + private JpaValidationSupportChainR4 myJpaValidationSupportChainR4; + @Autowired + @Qualifier("myLocationDaoR4") + protected IFhirResourceDao myLocationDao; + @Autowired + @Qualifier("myMediaDaoR4") + protected IFhirResourceDao myMediaDao; + @Autowired + @Qualifier("myMedicationAdministrationDaoR4") + protected IFhirResourceDao myMedicationAdministrationDao; + @Autowired + @Qualifier("myMedicationDaoR4") + protected IFhirResourceDao myMedicationDao; + @Autowired + @Qualifier("myMedicationRequestDaoR4") + protected IFhirResourceDao myMedicationRequestDao; + @Autowired + @Qualifier("myNamingSystemDaoR4") + protected IFhirResourceDao myNamingSystemDao; + @Autowired + @Qualifier("myObservationDaoR4") + protected IFhirResourceDao myObservationDao; + @Autowired + @Qualifier("myOperationDefinitionDaoR4") + protected IFhirResourceDao myOperationDefinitionDao; + @Autowired + @Qualifier("myOrganizationDaoR4") + protected IFhirResourceDao myOrganizationDao; + @Autowired + protected DatabaseBackedPagingProvider myPagingProvider; + @Autowired + @Qualifier("myPatientDaoR4") + protected IFhirResourceDaoPatient myPatientDao; + @Autowired + @Qualifier("myPractitionerDaoR4") + protected IFhirResourceDao myPractitionerDao; + @Autowired + @Qualifier("myProcedureRequestDaoR4") + protected IFhirResourceDao myProcedureRequestDao; + @Autowired + @Qualifier("myQuestionnaireDaoR4") + protected IFhirResourceDao myQuestionnaireDao; + @Autowired + @Qualifier("myQuestionnaireResponseDaoR4") + protected IFhirResourceDao myQuestionnaireResponseDao; + @Autowired + @Qualifier("myResourceProvidersR4") + protected Object myResourceProviders; + @Autowired + protected IResourceTableDao myResourceTableDao; + @Autowired + protected IResourceTagDao myResourceTagDao; + @Autowired + protected ISearchCoordinatorSvc mySearchCoordinatorSvc; + @Autowired + protected IFulltextSearchSvc mySearchDao; + @Autowired + protected ISearchDao mySearchEntityDao; + @Autowired + @Qualifier("mySearchParameterDaoR4") + protected IFhirResourceDao mySearchParameterDao; + @Autowired + protected ISearchParamPresenceSvc mySearchParamPresenceSvc; + @Autowired + protected ISearchParamRegistry mySearchParamRegsitry; + @Autowired + protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; + @Autowired + @Qualifier("myStructureDefinitionDaoR4") + protected IFhirResourceDao myStructureDefinitionDao; + @Autowired + @Qualifier("mySubscriptionDaoR4") + protected IFhirResourceDaoSubscription mySubscriptionDao; + @Autowired + @Qualifier("mySubstanceDaoR4") + protected IFhirResourceDao mySubstanceDao; + @Autowired + @Qualifier("mySystemDaoR4") + protected IFhirSystemDao mySystemDao; + @Autowired + @Qualifier("mySystemProviderR4") + protected JpaSystemProviderR4 mySystemProvider; + @Autowired + protected ITagDefinitionDao myTagDefinitionDao; + @Autowired + @Qualifier("myTaskDaoR4") + protected IFhirResourceDao myTaskDao; + @Autowired + protected IHapiTerminologySvc myTermSvc; + @Autowired + protected PlatformTransactionManager myTransactionMgr; + @Autowired + protected PlatformTransactionManager myTxManager; + @Autowired + @Qualifier("myJpaValidationSupportChainR4") + protected IValidationSupport myValidationSupport; + @Autowired + @Qualifier("myValueSetDaoR4") + protected IFhirResourceDaoValueSet myValueSetDao; + + @After() + public void afterCleanupDao() { + myDaoConfig.setExpireSearchResults(new DaoConfig().isExpireSearchResults()); + myDaoConfig.setExpireSearchResultsAfterMillis(new DaoConfig().getExpireSearchResultsAfterMillis()); + myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); + myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange()); + } + + @After() + public void afterGrabCaches() { + ourValueSetDao = myValueSetDao; + ourJpaValidationSupportChainR4 = myJpaValidationSupportChainR4; + } + + @Before + public void beforeCreateInterceptor() { + myInterceptor = mock(IServerInterceptor.class); + myDaoConfig.setInterceptors(myInterceptor); + } + + @Before + @Transactional + public void beforeFlushFT() { + FullTextEntityManager ftem = Search.getFullTextEntityManager(myEntityManager); + ftem.purgeAll(ResourceTable.class); + ftem.purgeAll(ResourceIndexedSearchParamString.class); + ftem.flushToIndexes(); + + myDaoConfig.setSchedulingDisabled(true); + } + + @Before + @Transactional() + public void beforePurgeDatabase() { + final EntityManager entityManager = this.myEntityManager; + purgeDatabase(entityManager, myTxManager, mySearchParamPresenceSvc, mySearchCoordinatorSvc); + } + + @Before + public void beforeResetConfig() { + myDaoConfig.setHardSearchLimit(1000); + myDaoConfig.setHardTagListLimit(1000); + myDaoConfig.setIncludeLimit(2000); + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + } + + @Override + protected FhirContext getContext() { + return myFhirCtx; + } + + protected T loadResourceFromClasspath(Class type, String resourceName) throws IOException { + InputStream stream = FhirResourceDaoDstu2SearchNoFtTest.class.getResourceAsStream(resourceName); + if (stream == null) { + fail("Unable to load resource: " + resourceName); + } + String string = IOUtils.toString(stream, "UTF-8"); + IParser newJsonParser = EncodingEnum.detectEncodingNoDefault(string).newParser(myFhirCtx); + return newJsonParser.parseResource(type, string); + } + + public TransactionTemplate newTxTemplate() { + TransactionTemplate retVal = new TransactionTemplate(myTxManager); + retVal.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + retVal.afterPropertiesSet(); + return retVal; + } + + @AfterClass + public static void afterClassClearContextBaseJpaR4Test() throws Exception { + ourValueSetDao.purgeCaches(); + ourJpaValidationSupportChainR4.flush(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + public static String toSearchUuidFromLinkNext(Bundle theBundle) { + String linkNext = theBundle.getLink("next").getUrl(); + linkNext = linkNext.substring(linkNext.indexOf('?')); + Map params = UrlUtil.parseQueryString(linkNext); + String[] uuidParams = params.get(Constants.PARAM_PAGINGACTION); + String uuid = uuidParams[0]; + return uuid; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCustomTypeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCustomTypeR4Test.java new file mode 100644 index 00000000000..0b36153b727 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCustomTypeR4Test.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.assertEquals; + +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; + +@SuppressWarnings({ }) +public class FhirResourceDaoCustomTypeR4Test extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCustomTypeR4Test.class); + + @Before + public void before() { + myFhirCtx.setDefaultTypeForProfile(CustomObservationR4.PROFILE, CustomObservationR4.class); + } + + @Test + public void testSaveAndRestore() { + CustomObservationR4 obs = new CustomObservationR4(); + obs.setEyeColour(new StringType("blue")); + + IIdType id = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + CustomObservationR4 read = (CustomObservationR4) myObservationDao.read(id); + assertEquals("blue", read.getEyeColour().getValue()); + + IBundleProvider found = myObservationDao.search(new SearchParameterMap()); + assertEquals(1, found.size().intValue()); + CustomObservationR4 search = (CustomObservationR4) found.getResources(0, 1).get(0); + assertEquals("blue", search.getEyeColour().getValue()); + + } + + @After + public void after() { + myFhirCtx.setDefaultTypeForProfile(CustomObservationR4.PROFILE, null); + } +} 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 new file mode 100644 index 00000000000..52070c17146 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import java.nio.charset.StandardCharsets; + +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.dao.DaoMethodOutcome; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoDocumentR4Test extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDocumentR4Test.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Test + public void testPostDocument() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/sample-document.xml"), StandardCharsets.UTF_8); + Bundle inputBundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + DaoMethodOutcome responseBundle = myBundleDao.create(inputBundle, mySrd); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java new file mode 100644 index 00000000000..7f0d3df237a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.assertNotEquals; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.r4.model.CodeSystem; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvc; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4CodeSystemTest.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(false); + } + + + @Test + public void testIndexContained() throws Exception { + BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(true); + + String input = IOUtils.toString(getClass().getResource("/r4_codesystem_complete.json"), StandardCharsets.UTF_8); + CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input); + myCodeSystemDao.create(cs, mySrd); + + + mySystemDao.markAllResourcesForReindexing(); + + int outcome = mySystemDao.performReindexingPass(100); + assertNotEquals(-1, outcome); // -1 means there was a failure + + myTermSvc.saveDeferred(); + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java new file mode 100644 index 00000000000..39a279b991f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.dao.r4; + +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.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Test + public void before() { + myDaoConfig.setIndexContainedResources(true); + } + + @Test + public void testIndexContained() { + Patient p = new Patient(); + p.setId("#some_patient"); + p.addName().setFamily("MYFAMILY").addGiven("MYGIVEN"); + + Observation o1 = new Observation(); + o1.getCode().setText("Some Observation"); + o1.setSubject(new Reference(p)); + IIdType oid1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getCode().setText("Some Observation"); + o2.setSubject(new Reference(p)); + IIdType oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("MYFAMILY").addGiven("MYGIVEN"); + IIdType pid2 = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(o2)); + + + SearchParameterMap map; + +// map = new SearchParameterMap(); +// map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT)); +// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); + + } + + + // TODO: make sure match URLs don't delete + +} 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 new file mode 100644 index 00000000000..58298c72404 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java @@ -0,0 +1,163 @@ +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.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4ExternalReferenceTest extends BaseJpaR4Test { + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @Before + @After + public void resetDefaultBehaviour() { + // Reset to default + myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setTreatBaseUrlsAsLocal(null); + } + + @Test + public void testInternalReferenceBlockedByDefault() { + Patient p = new Patient(); + p.getManagingOrganization().setReference("Organization/FOO"); + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/FOO not found, specified in path: Patient.managingOrganization", e.getMessage()); + } + } + + @Test + public void testExternalReferenceBlockedByDefault() { + Organization org = new Organization(); + org.setId("FOO"); + org.setName("Org Name"); + myOrganizationDao.update(org, mySrd); + + Patient p = new Patient(); + p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO"); + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource contains external reference to URL \"http://example.com/base/Organization/FOO\" but this server is not configured to allow external references", e.getMessage()); + } + } + + @Test + public void testExternalReferenceAllowed() { + Organization org = new Organization(); + org.setId("FOO"); + org.setName("Org Name"); + myOrganizationDao.update(org, mySrd); + + myDaoConfig.setAllowExternalReferences(true); + + Patient p = new Patient(); + p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO"); + IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example.com/base/Organization/FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(pid.getValue())); + + map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example2.com/base/Organization/FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + } + + @Test + public void testExternalReferenceReplaced() { + Organization org = new Organization(); + org.setId("FOO"); + org.setName("Org Name"); + org.getPartOf().setDisplay("Parent"); // <-- no reference, make sure this works + myOrganizationDao.update(org, mySrd); + + Set urls = new HashSet(); + urls.add("http://example.com/base/"); + myDaoConfig.setTreatBaseUrlsAsLocal(urls); + + Patient p = new Patient(); + p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO"); + IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = myPatientDao.read(pid, mySrd); + assertEquals("Organization/FOO", p.getManagingOrganization().getReference()); + + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example.com/base/Organization/FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(pid.getValue())); + } + + @Test + public void testSearchForInvalidLocalReference() { + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("Organization/FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + + map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("Organization/9999999999")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + } + + @Test + public void testExternalReferenceReplacedWrongDoesntMatch() { + Organization org = new Organization(); + org.setId("FOO"); + org.setName("Org Name"); + org.getPartOf().setDisplay("Parent"); // <-- no reference, make sure this works + myOrganizationDao.update(org, mySrd); + + Set urls = new HashSet(); + urls.add("http://example.com/base/"); + myDaoConfig.setTreatBaseUrlsAsLocal(urls); + + Patient p = new Patient(); + p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO"); + IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = myPatientDao.read(pid, mySrd); + assertEquals("Organization/FOO", p.getManagingOrganization().getReference()); + + SearchParameterMap map; + + // Different base + map = new SearchParameterMap(); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://foo.com/base/Organization/FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java new file mode 100644 index 00000000000..37352a6e185 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java @@ -0,0 +1,473 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; +import ca.uhn.fhir.jpa.interceptor.JpaServerInterceptorAdapter; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4InterceptorTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4InterceptorTest.class); + private IJpaServerInterceptor myJpaInterceptor; + private JpaServerInterceptorAdapter myJpaInterceptorAdapter = new JpaServerInterceptorAdapter(); + private IServerOperationInterceptor myServerOperationInterceptor; + + @After + public void after() { + myDaoConfig.getInterceptors().remove(myJpaInterceptor); + myDaoConfig.getInterceptors().remove(myJpaInterceptorAdapter); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + } + + @Before + public void before() { + myJpaInterceptor = mock(IJpaServerInterceptor.class); + + myServerOperationInterceptor = mock(IServerOperationInterceptor.class, new Answer() { + @Override + public Object answer(InvocationOnMock theInvocation) throws Throwable { + if (theInvocation.getMethod().getReturnType().equals(boolean.class)) { + return true; + } + return null; + } + }); + + myDaoConfig.getInterceptors().add(myJpaInterceptor); + myDaoConfig.getInterceptors().add(myJpaInterceptorAdapter); + myDaoConfig.getInterceptors().add(myServerOperationInterceptor); + } + + @Test + public void testJpaCreate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + ArgumentCaptor detailsCapt; + ArgumentCaptor tableCapt; + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); + assertNotNull(tableCapt.getValue().getId()); + assertEquals(id, tableCapt.getValue().getId()); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); + + /* + * Not do a conditional create + */ + p = new Patient(); + p.addName().setFamily("PATIENT1"); + Long id2 = myPatientDao.create(p, "Patient?family=PATIENT", mySrd).getId().getIdPartAsLong(); + assertEquals(id, id2); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); + verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); + + } + + /* + * ***************************************************** + * Note that non JPA specific operations get tested in individual + * operation test methods too + * ***************************************************** + */ + + @Test + public void testJpaDelete() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + myPatientDao.delete(new IdType("Patient", id), mySrd); + + ArgumentCaptor detailsCapt; + ArgumentCaptor tableCapt; + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(1)).resourceDeleted(detailsCapt.capture(), tableCapt.capture()); + assertNotNull(tableCapt.getValue().getId()); + assertEquals(id, tableCapt.getValue().getId()); + + } + + @Test + public void testJpaUpdate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.setId(new IdType(id)); + p.addName().setFamily("PATIENT1"); + Long id2 = myPatientDao.update(p, mySrd).getId().getIdPartAsLong(); + assertEquals(id, id2); + + ArgumentCaptor detailsCapt; + ArgumentCaptor tableCapt; + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(1)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); + assertNotNull(tableCapt.getValue().getId()); + assertEquals(id, tableCapt.getValue().getId()); + + /* + * Now do a conditional update + */ + + p = new Patient(); + p.setId(new IdType(id)); + p.addName().setFamily("PATIENT2"); + id2 = myPatientDao.update(p, "Patient?family=PATIENT1", mySrd).getId().getIdPartAsLong(); + assertEquals(id, id2); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); + verify(myJpaInterceptor, times(2)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); + assertEquals(id, tableCapt.getAllValues().get(2).getId()); + + /* + * Now do a conditional update where none will match (so this is actually a create) + */ + + p = new Patient(); + p.addName().setFamily("PATIENT3"); + id2 = myPatientDao.update(p, "Patient?family=ZZZ", mySrd).getId().getIdPartAsLong(); + assertNotEquals(id, id2); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + tableCapt = ArgumentCaptor.forClass(ResourceTable.class); + verify(myJpaInterceptor, times(2)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); + verify(myJpaInterceptor, times(2)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); + assertEquals(id2, tableCapt.getAllValues().get(3).getId()); + + } + + @Test + public void testRequestOperationCreate() { + IServerOperationInterceptor interceptor = mock(IServerOperationInterceptor.class); + myServerInterceptorList.add(interceptor); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + IIdType id = myPatientDao.create(p, mySrd).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testServerOperationCreate() { + verify(myServerOperationInterceptor, times(0)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + IIdType id = myPatientDao.create(p, (RequestDetails)null).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + + verify(myServerOperationInterceptor, times(1)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + } + + @SuppressWarnings("deprecation") + @Test + public void testServerOperationUpdate() { + verify(myServerOperationInterceptor, times(0)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(0)).resourceUpdated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(0)).resourceUpdated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + IIdType id = myPatientDao.create(p, (RequestDetails)null).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + + p.addName().setFamily("2"); + myPatientDao.update(p); + + verify(myServerOperationInterceptor, times(1)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(1)).resourceUpdated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(1)).resourceUpdated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); + } + + @Test + public void testServerOperationDelete() { + verify(myServerOperationInterceptor, times(0)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(0)).resourceDeleted(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + IIdType id = myPatientDao.create(p, (RequestDetails)null).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + + p.addName().setFamily("2"); + myPatientDao.delete(p.getIdElement().toUnqualifiedVersionless()); + + verify(myServerOperationInterceptor, times(1)).resourceCreated(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + verify(myServerOperationInterceptor, times(1)).resourceDeleted(Mockito.isNull(RequestDetails.class), any(IBaseResource.class)); + } + + @Test + public void testRequestOperationDelete() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + IIdType newId = myPatientDao.delete(new IdType("Patient/" + id), mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + DeleteMethodOutcome outcome = myPatientDao.deleteByUrl("Patient?name=PATIENT", mySrd); + String oo = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); + ourLog.info(oo); + assertThat(oo, containsString("deleted 2 resource(s)")); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionCreate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(HTTPVerb.POST); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(1L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDelete() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerb.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient?name=PATIENT") + .setMethod(HTTPVerb.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + String oo = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(oo); + assertThat(oo, containsString("deleted 2 resource(s)")); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionUpdate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.setId(new IdType("Patient/" + id)); + p.addName().setFamily("PATIENT2"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[1]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class), any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerb.PUT); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class), any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationUpdate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + res = (IBaseResource) theInvocation.getArguments()[1]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class), any(IBaseResource.class)); + + p = new Patient(); + p.setId(new IdType("Patient/" + id)); + p.addName().setFamily("PATIENT2"); + IIdType newId = myPatientDao.update(p, mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class), any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java new file mode 100644 index 00000000000..da97ccd3d21 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java @@ -0,0 +1,99 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test { + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @After + public void afterResetConfig() { + myDaoConfig.setEnforceReferentialIntegrityOnWrite(new DaoConfig().isEnforceReferentialIntegrityOnWrite()); + myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete()); + } + + @Test + public void testCreateUnknownReferenceFail() throws Exception { + + Patient p = new Patient(); + p.setManagingOrganization(new Reference("Organization/AAA")); + try { + myPatientDao.create(p); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/AAA not found, specified in path: Patient.managingOrganization", e.getMessage()); + } + + } + + @Test + public void testCreateUnknownReferenceAllow() throws Exception { + myDaoConfig.setEnforceReferentialIntegrityOnWrite(false); + + Patient p = new Patient(); + p.setManagingOrganization(new Reference("Organization/AAA")); + IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + p = myPatientDao.read(id); + assertEquals("Organization/AAA", p.getManagingOrganization().getReference()); + + } + + @Test + public void testDeleteFail() throws Exception { + Organization o = new Organization(); + o.setName("FOO"); + IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.setManagingOrganization(new Reference(oid)); + IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + try { + myOrganizationDao.delete(oid); + fail(); + } catch (ResourceVersionConflictException e) { + assertEquals("Unable to delete Organization/"+oid.getIdPart()+" because at least one resource has a reference to this resource. First reference found was resource Organization/"+oid.getIdPart()+" in path Patient.managingOrganization", e.getMessage()); + } + + myPatientDao.delete(pid); + myOrganizationDao.delete(oid); + + } + + @Test + public void testDeleteAllow() throws Exception { + myDaoConfig.setEnforceReferentialIntegrityOnDelete(false); + + Organization o = new Organization(); + o.setName("FOO"); + IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.setManagingOrganization(new Reference(oid)); + IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + myOrganizationDao.delete(oid); + myPatientDao.delete(pid); + + } + + +} 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 new file mode 100644 index 00000000000..c449a1fc8b7 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java @@ -0,0 +1,883 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Appointment.AppointmentStatus; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.jpa.dao.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.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class); + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Test + public void testCreateInvalidParamInvalidResourceName() { + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("PatientFoo.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Invalid SearchParameter.expression value \"PatientFoo.gender\": Unknown resource name \"PatientFoo\" (this name is not known in FHIR version \"R4\")", e.getMessage()); + } + } + + @Test + public void testCreateInvalidNoBase() { + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("SearchParameter.base is missing", e.getMessage()); + } + } + + @Test + public void testCreateInvalidParamMismatchedResourceName() { + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender or Observation.code"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Invalid SearchParameter.expression value \"Observation.code\". All paths in a single SearchParameter must match the same resource type", e.getMessage()); + } + } + + @Test + public void testCreateInvalidParamNoPath() { + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("SearchParameter.expression is missing", e.getMessage()); + } + } + + @Test + public void testCreateInvalidParamNoResourceName() { + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Invalid SearchParameter.expression value \"gender\". Must start with a resource name", e.getMessage()); + } + } + + @Test + public void testCreateInvalidParamParamNullStatus() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(null); + try { + mySearchParameterDao.create(fooSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("SearchParameter.status is missing or invalid: null", e.getMessage()); + } + + } + + @Test + public void testExtensionWithNoValueIndexesWithoutFailure() { + SearchParameter eyeColourSp = new SearchParameter(); + eyeColourSp.addBase("Patient"); + eyeColourSp.setCode("eyecolour"); + eyeColourSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + eyeColourSp.setTitle("Eye Colour"); + eyeColourSp.setExpression("Patient.extension('http://acme.org/eyecolour')"); + eyeColourSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + eyeColourSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(eyeColourSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient p1 = new Patient(); + p1.setActive(true); + p1.addExtension().setUrl("http://acme.org/eyecolour").addExtension().setUrl("http://foo").setValue(new StringType("VAL")); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + } + + @Test + public void testSearchForExtensionToken() { + SearchParameter eyeColourSp = new SearchParameter(); + eyeColourSp.addBase("Patient"); + eyeColourSp.setCode("eyecolour"); + eyeColourSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + eyeColourSp.setTitle("Eye Colour"); + eyeColourSp.setExpression("Patient.extension('http://acme.org/eyecolour')"); + eyeColourSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + eyeColourSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(eyeColourSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient p1 = new Patient(); + p1.setActive(true); + p1.addExtension().setUrl("http://acme.org/eyecolour").setValue(new CodeType("blue")); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.setActive(true); + p2.addExtension().setUrl("http://acme.org/eyecolour").setValue(new CodeType("green")); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + // Try with custom gender SP + SearchParameterMap map = new SearchParameterMap(); + map.add("eyecolour", new TokenParam(null, "blue")); + IBundleProvider results = myPatientDao.search(map); + List foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p1id.getValue())); + + } + + @Test + public void testSearchForExtensionTwoDeepCoding() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Organization")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new Coding().setSystem("foo").setCode("bar")); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new TokenParam("foo", "bar")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepCodeableConcept() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Organization")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new CodeableConcept().addCoding(new Coding().setSystem("foo").setCode("bar"))); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new TokenParam("foo", "bar")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepReferenceWrongType() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Observation")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Appointment apt = new Appointment(); + apt.setStatus(AppointmentStatus.ARRIVED); + IIdType aptId = myAppointmentDao.create(apt).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new Reference(aptId.getValue())); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new ReferenceParam(aptId.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, empty()); + } + + @Test + public void testSearchForExtensionTwoDeepReferenceWithoutType() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Appointment apt = new Appointment(); + apt.setStatus(AppointmentStatus.ARRIVED); + IIdType aptId = myAppointmentDao.create(apt).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new Reference(aptId.getValue())); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new ReferenceParam(aptId.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepReference() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Appointment")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Appointment apt = new Appointment(); + apt.setStatus(AppointmentStatus.ARRIVED); + IIdType aptId = myAppointmentDao.create(apt).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new Reference(aptId.getValue())); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new ReferenceParam(aptId.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepDate() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.DATE); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Appointment apt = new Appointment(); + apt.setStatus(AppointmentStatus.ARRIVED); + IIdType aptId = myAppointmentDao.create(apt).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new DateType("2012-01-02")); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new DateParam("2012-01-02")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepNumber() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.NUMBER); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new IntegerType(5)); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new NumberParam("5")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepDecimal() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.NUMBER); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new DecimalType("2.1")); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new NumberParam("2.1")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionTwoDeepString() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("foobar"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.STRING); + siblingSp.setTitle("FooBar"); + siblingSp.setExpression("Patient.extension('http://acme.org/foo').extension('http://acme.org/bar')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient patient = new Patient(); + patient.addName().setFamily("P2"); + Extension extParent = patient + .addExtension() + .setUrl("http://acme.org/foo"); + extParent + .addExtension() + .setUrl("http://acme.org/bar") + .setValue(new StringType("HELLOHELLO")); + + IIdType p2id = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.add("foobar", new StringParam("hello")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + } + + @Test + public void testSearchForExtensionReferenceWithNonMatchingTarget() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("sibling"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("Sibling"); + siblingSp.setExpression("Patient.extension('http://acme.org/sibling')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Organization")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient p1 = new Patient(); + p1.addName().setFamily("P1"); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("P2"); + p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id)); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + // Search by ref + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam(p1id.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, empty()); + + // Search by chain + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam("name", "P1")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, empty()); + + } + + + + @Test + public void testSearchForExtensionReferenceWithoutTarget() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("sibling"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("Sibling"); + siblingSp.setExpression("Patient.extension('http://acme.org/sibling')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient p1 = new Patient(); + p1.addName().setFamily("P1"); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("P2"); + p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id)); + + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + Appointment app = new Appointment(); + app.addParticipant().getActor().setReference(p2id.getValue()); + IIdType appid = myAppointmentDao.create(app).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + // Search by ref + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam(p1id.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + + // Search by chain + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam("name", "P1")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + + // Search by two level chain + map = new SearchParameterMap(); + map.add("patient", new ReferenceParam("sibling.name", "P1")); + results = myAppointmentDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, containsInAnyOrder(appid.getValue())); + + + } + + @Test + public void testSearchForExtensionReferenceWithTarget() { + SearchParameter siblingSp = new SearchParameter(); + siblingSp.addBase("Patient"); + siblingSp.setCode("sibling"); + siblingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + siblingSp.setTitle("Sibling"); + siblingSp.setExpression("Patient.extension('http://acme.org/sibling')"); + siblingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + siblingSp.getTarget().add(new CodeType("Patient")); + mySearchParameterDao.create(siblingSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient p1 = new Patient(); + p1.addName().setFamily("P1"); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("P2"); + p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id)); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + Appointment app = new Appointment(); + app.addParticipant().getActor().setReference(p2id.getValue()); + IIdType appid = myAppointmentDao.create(app).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + // Search by ref + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam(p1id.getValue())); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + + // Search by chain + map = new SearchParameterMap(); + map.add("sibling", new ReferenceParam("name", "P1")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(p2id.getValue())); + + // Search by two level chain + map = new SearchParameterMap(); + map.add("patient", new ReferenceParam("sibling.name", "P1")); + results = myAppointmentDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, containsInAnyOrder(appid.getValue())); + + } + + @Test + public void testIncludeExtensionReferenceAsRecurse() { + SearchParameter attendingSp = new SearchParameter(); + attendingSp.addBase("Patient"); + attendingSp.setCode("attending"); + attendingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + attendingSp.setTitle("Attending"); + attendingSp.setExpression("Patient.extension('http://acme.org/attending')"); + attendingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + attendingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + attendingSp.getTarget().add(new CodeType("Practitioner")); + IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless(); + + mySearchParamRegsitry.forceRefresh(); + + Practitioner p1 = new Practitioner(); + p1.addName().setFamily("P1"); + IIdType p1id = myPractitionerDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("P2"); + p2.addExtension().setUrl("http://acme.org/attending").setValue(new Reference(p1id)); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + Appointment app = new Appointment(); + app.addParticipant().getActor().setReference(p2id.getValue()); + IIdType appId = myAppointmentDao.create(app).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + map = new SearchParameterMap(); + map.addInclude(new Include("Appointment:patient", true)); + map.addInclude(new Include("Patient:attending", true)); + results = myAppointmentDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(appId.getValue(), p2id.getValue(), p1id.getValue())); + + } + + @Test + public void testSearchWithCustomParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless(); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + // Try with custom gender SP + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + // Try with normal gender SP + map = new SearchParameterMap(); + map.add("gender", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + // Delete the param + mySearchParameterDao.delete(spId, mySrd); + + mySearchParamRegsitry.forceRefresh(); + mySystemDao.performReindexingPass(100); + + // Try with custom gender SP + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + try { + myPatientDao.search(map).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unknown search parameter foo for resource type Patient", e.getMessage()); + } + } + + @Test + public void testSearchWithCustomParamDraft() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.DRAFT); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + // Try with custom gender SP (should find nothing) + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + try { + myPatientDao.search(map).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unknown search parameter foo for resource type Patient", e.getMessage()); + } + + // Try with normal gender SP + map = new SearchParameterMap(); + map.add("gender", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + } + + @Test + public void testCustomReferenceParameter() throws Exception { + SearchParameter sp = new SearchParameter(); + sp.addBase("Patient"); + sp.setCode("myDoctor"); + sp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + sp.setTitle("My Doctor"); + sp.setExpression("Patient.extension('http://fmcna.com/myDoctor')"); + sp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + sp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(sp); + + org.hl7.fhir.r4.model.Practitioner pract = new org.hl7.fhir.r4.model.Practitioner(); + pract.setId("A"); + pract.addName().setFamily("PRACT"); + myPractitionerDao.update(pract); + + Patient pat = myFhirCtx.newJsonParser().parseResource(Patient.class, loadClasspath("/r4_custom_resource_patient.json")); + IIdType pid = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add("myDoctor", new ReferenceParam("A")); + IBundleProvider outcome = myPatientDao.search(params); + List ids = toUnqualifiedVersionlessIdValues(outcome); + ourLog.info("IDS: " + ids); + assertThat(ids, contains(pid.getValue())); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java new file mode 100644 index 00000000000..8b8ef59b647 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java @@ -0,0 +1,565 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchFtTest.class); + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Test + public void testCodeTextSearch() { + Observation obs1 = new Observation(); + obs1.getCode().setText("Systolic Blood Pressure"); + obs1.setStatus(ObservationStatus.FINAL); + obs1.setValue(new Quantity(123)); + obs1.setComment("obs1"); + IIdType id1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getCode().setText("Diastolic Blood Pressure"); + obs2.setStatus(ObservationStatus.FINAL); + obs2.setValue(new Quantity(81)); + IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(Observation.SP_CODE, new TokenParam(null, "systolic").setModifier(TokenParamModifier.TEXT)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1))); + +// map = new SearchParameterMap(); +// map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); +// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); +// +// map = new SearchParameterMap(); +// map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); +// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); +// +// map = new SearchParameterMap(); +// map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); +// map.add(Constants.PARAM_CONTENT, new StringParam("obs1")); +// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1))); + + } + + + @Test + public void testResourceTextSearch() { + Observation obs1 = new Observation(); + obs1.getCode().setText("Systolic Blood Pressure"); + obs1.setStatus(ObservationStatus.FINAL); + obs1.setValue(new Quantity(123)); + obs1.setComment("obs1"); + IIdType id1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getCode().setText("Diastolic Blood Pressure"); + obs2.setStatus(ObservationStatus.FINAL); + obs2.setValue(new Quantity(81)); + IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("systolic")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("blood")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("obs1")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1))); + + } + + private ServletRequestDetails mockSrd() { + return mySrd; + } + + @Test + @Ignore + public void testStringTextSearch() { + Observation obs1 = new Observation(); + obs1.getCode().setText("AAAAA"); + obs1.setValue(new StringType("Systolic Blood Pressure")); + obs1.setStatus(ObservationStatus.FINAL); + IIdType id1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs1.getCode().setText("AAAAA"); + obs1.setValue(new StringType("Diastolic Blood Pressure")); + obs2.setStatus(ObservationStatus.FINAL); + IIdType id2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(Observation.SP_VALUE_STRING, new StringParam("sure").setContains(true)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); + + } + + + @Test + public void testSuggestIgnoresBase64Content() { + Patient patient = new Patient(); + patient.addName().setFamily("testSuggest"); + IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + + Media med = new Media(); + med.getSubject().setReferenceElement(ptId); + med.getSubtype().setText("Systolic Blood Pressure"); + med.getContent().setContentType("LCws"); + med.getContent().setDataElement(new Base64BinaryType(new byte[] {44,44,44,44,44,44,44,44})); + med.getContent().setTitle("bbbb syst"); + myMediaDao.create(med, mockSrd()); + ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(med)); + + List output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "press"); + ourLog.info("Found: " + output); + assertEquals(2, output.size()); + assertEquals("Pressure", output.get(0).getTerm()); + assertEquals("Systolic Blood Pressure", output.get(1).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "prezure"); + ourLog.info("Found: " + output); + assertEquals(2, output.size()); + assertEquals("Pressure", output.get(0).getTerm()); + assertEquals("Systolic Blood Pressure", output.get(1).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "syst"); + ourLog.info("Found: " + output); + assertEquals(4, output.size()); + assertEquals("syst", output.get(0).getTerm()); + assertEquals("bbbb syst", output.get(1).getTerm()); + assertEquals("Systolic", output.get(2).getTerm()); + assertEquals("Systolic Blood Pressure", output.get(3).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "LCws"); + ourLog.info("Found: " + output); + assertEquals(0, output.size()); + } + + @Test + public void testSuggest() { + Patient patient = new Patient(); + patient.addName().setFamily("testSuggest"); + IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(ptId); + obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); + myObservationDao.create(obs, mockSrd()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(ptId); + obs.getCode().setText("MNBVCXZ"); + myObservationDao.create(obs, mockSrd()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(ptId); + obs.getCode().setText("ZXC HELLO"); + obs.addComponent().getCode().setText("HHHHHHHHHH"); + myObservationDao.create(obs, mockSrd()); + + /* + * These shouldn't match since they're for another patient + */ + patient = new Patient(); + patient.addName().setFamily("testSuggest2"); + IIdType ptId2 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getSubject().setReferenceElement(ptId2); + obs2.getCode().setText("ZXCVBNMZZ"); + myObservationDao.create(obs2, mockSrd()); + + List output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXCVBNM"); + ourLog.info("Found: " + output); + assertEquals(4, output.size()); + assertEquals("ZXCVBNM", output.get(0).getTerm()); + assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(1).getTerm()); + assertEquals("ZXC", output.get(2).getTerm()); + assertEquals("ZXC HELLO", output.get(3).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXC"); + ourLog.info("Found: " + output); + assertEquals(4, output.size()); + assertEquals("ZXC", output.get(0).getTerm()); + assertEquals("ZXC HELLO", output.get(1).getTerm()); + assertEquals("ZXCVBNM", output.get(2).getTerm()); + assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(3).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "HELO"); + ourLog.info("Found: " + output); + assertEquals(2, output.size()); + assertEquals("HELLO", output.get(0).getTerm()); + assertEquals("ZXC HELLO", output.get(1).getTerm()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "Z"); + ourLog.info("Found: " + output); + assertEquals(0, output.size()); + + output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZX"); + ourLog.info("Found: " + output); + assertEquals(2, output.size()); + assertEquals("ZXC", output.get(0).getTerm()); + assertEquals("ZXC HELLO", output.get(1).getTerm()); + + } + + + @Test + public void testSearchAndReindex() { + Patient patient; + SearchParameterMap map; + + patient = new Patient(); + patient.getText().setDivAsString("
DIVAAA
"); + patient.addName().addGiven("NAMEAAA"); + IIdType pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_TEXT, new StringParam("DIVAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + /* + * Reindex + */ + + patient = new Patient(); + patient.setId(pId1); + patient.getText().setDivAsString("
DIVBBB
"); + patient.addName().addGiven("NAMEBBB"); + myPatientDao.update(patient, mockSrd()); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + + map = new SearchParameterMap(); + map.add(Patient.SP_NAME, new StringParam("NAMEBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_TEXT, new StringParam("DIVBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + } + + @Test + public void testEverythingInstanceWithContentFilter() { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId1 = myPatientDao.create(pt1, mockSrd()).getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId2 = myPatientDao.create(pt2, mockSrd()).getId().toUnqualifiedVersionless(); + + Device dev1 = new Device(); + dev1.setManufacturer("Some Manufacturer"); + IIdType devId1 = myDeviceDao.create(dev1, mockSrd()).getId().toUnqualifiedVersionless(); + + Device dev2 = new Device(); + dev2.setManufacturer("Some Manufacturer 2"); + myDeviceDao.create(dev2, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getText().setDivAsString("
OBSTEXT1
"); + obs1.getSubject().setReferenceElement(ptId1); + obs1.getCode().addCoding().setCode("CODE1"); + obs1.setValue(new StringType("obsvalue1")); + obs1.getDevice().setReferenceElement(devId1); + IIdType obsId1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getSubject().setReferenceElement(ptId1); + obs2.getCode().addCoding().setCode("CODE2"); + obs2.setValue(new StringType("obsvalue2")); + IIdType obsId2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs3 = new Observation(); + obs3.getSubject().setReferenceElement(ptId2); + obs3.getCode().addCoding().setCode("CODE3"); + obs3.setValue(new StringType("obsvalue3")); + IIdType obsId3 = myObservationDao.create(obs3, mockSrd()).getId().toUnqualifiedVersionless(); + + HttpServletRequest request; + List actual; + request = mock(HttpServletRequest.class); + StringAndListParam param; + + ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()}); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); + + request = mock(HttpServletRequest.class); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1))); + + /* + * Add another match + */ + + Observation obs4 = new Observation(); + obs4.getSubject().setReferenceElement(ptId1); + obs4.getCode().addCoding().setCode("CODE1"); + obs4.setValue(new StringType("obsvalue1")); + IIdType obsId4 = myObservationDao.create(obs4, mockSrd()).getId().toUnqualifiedVersionless(); + assertNotEquals(obsId1.getIdPart(), obsId4.getIdPart(), devId1); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); + + /* + * Make one previous match no longer match + */ + + obs1 = new Observation(); + obs1.setId(obsId1); + obs1.getSubject().setReferenceElement(ptId1); + obs1.getCode().addCoding().setCode("CODE2"); + obs1.setValue(new StringType("obsvalue2")); + myObservationDao.update(obs1, mockSrd()); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); + + } + + @Test + public void testEverythingTypeWithContentFilter() { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId1 = myPatientDao.create(pt1, mockSrd()).getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId2 = myPatientDao.create(pt2, mockSrd()).getId().toUnqualifiedVersionless(); + + Device dev1 = new Device(); + dev1.setManufacturer("Some Manufacturer"); + IIdType devId1 = myDeviceDao.create(dev1, mockSrd()).getId().toUnqualifiedVersionless(); + + Device dev2 = new Device(); + dev2.setManufacturer("Some Manufacturer 2"); + myDeviceDao.create(dev2, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getSubject().setReferenceElement(ptId1); + obs1.getCode().addCoding().setCode("CODE1"); + obs1.setValue(new StringType("obsvalue1")); + obs1.getDevice().setReferenceElement(devId1); + IIdType obsId1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getSubject().setReferenceElement(ptId1); + obs2.getCode().addCoding().setCode("CODE2"); + obs2.setValue(new StringType("obsvalue2")); + IIdType obsId2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); + + Observation obs3 = new Observation(); + obs3.getSubject().setReferenceElement(ptId2); + obs3.getCode().addCoding().setCode("CODE3"); + obs3.setValue(new StringType("obsvalue3")); + IIdType obsId3 = myObservationDao.create(obs3, mockSrd()).getId().toUnqualifiedVersionless(); + + HttpServletRequest request; + List actual; + request = mock(HttpServletRequest.class); + StringAndListParam param; + + ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()}); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); + + request = mock(HttpServletRequest.class); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3))); + + /* + * Add another match + */ + + Observation obs4 = new Observation(); + obs4.getSubject().setReferenceElement(ptId1); + obs4.getCode().addCoding().setCode("CODE1"); + obs4.setValue(new StringType("obsvalue1")); + IIdType obsId4 = myObservationDao.create(obs4, mockSrd()).getId().toUnqualifiedVersionless(); + assertNotEquals(obsId1.getIdPart(), obsId4.getIdPart(), devId1); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); + + /* + * Make one previous match no longer match + */ + + obs1 = new Observation(); + obs1.setId(obsId1); + obs1.getSubject().setReferenceElement(ptId1); + obs1.getCode().addCoding().setCode("CODE2"); + obs1.setValue(new StringType("obsvalue2")); + myObservationDao.update(obs1, mockSrd()); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); + + } + + + /** + * When processing transactions, we do two passes. Make sure we don't update the lucene index twice since that would + * be inefficient + */ + @Test + public void testSearchDontReindexForUpdateWithIndexDisabled() { + Patient patient; + SearchParameterMap map; + + patient = new Patient(); + patient.getText().setDivAsString("
DIVAAA
"); + patient.addName().addGiven("NAMEAAA"); + IIdType pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_TEXT, new StringParam("DIVAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + /* + * Update but don't reindex + */ + + patient = new Patient(); + patient.setId(pId1); + patient.getText().setDivAsString("
DIVBBB
"); + patient.addName().addGiven("NAMEBBB"); + myPatientDao.update(patient, null, false, mockSrd()); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), not(contains(toValues(pId1)))); + + myPatientDao.update(patient, null, true, mockSrd()); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); + + map = new SearchParameterMap(); + map.add(Patient.SP_NAME, new StringParam("NAMEBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam("NAMEBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + map = new SearchParameterMap(); + map.add(Constants.PARAM_TEXT, new StringParam("DIVBBB")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(pId1))); + + } + + @Test + public void testSearchWithChainedParams() { + String methodName = "testSearchWithChainedParams"; + IIdType pId1; + { + Patient patient = new Patient(); + patient.addName().addGiven(methodName); + patient.addAddress().addLine("My fulltext address"); + pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); + } + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId1); + obs.setValue(new StringType("This is the FULLtext of the observation")); + IIdType oId1 = myObservationDao.create(obs, mockSrd()).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId1); + obs.setValue(new StringType("Another fullText")); + IIdType oId2 = myObservationDao.create(obs, mockSrd()).getId().toUnqualifiedVersionless(); + + List patients; + SearchParameterMap params; + + params = new SearchParameterMap(); + params.add(Constants.PARAM_CONTENT, new StringParam("fulltext")); + patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params)); + assertThat(patients, containsInAnyOrder(toValues(pId1))); + + params = new SearchParameterMap(); + params.add(Constants.PARAM_CONTENT, new StringParam("FULLTEXT")); + patients = toUnqualifiedVersionlessIdValues(myObservationDao.search(params)); + assertThat(patients, containsInAnyOrder(toValues(oId1, oId2))); + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java new file mode 100644 index 00000000000..cadf3883c9f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -0,0 +1,3440 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.*; +import org.junit.*; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.api.Constants; +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.param.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +@SuppressWarnings("unchecked") +public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchNoFtTest.class); + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum()); + } + + /** + * See #441 + */ + @Test + public void testChainedMedication() { + Medication medication = new Medication(); + medication.getCode().addCoding().setSystem("SYSTEM").setCode("04823543"); + IIdType medId = myMedicationDao.create(medication).getId().toUnqualifiedVersionless(); + + MedicationAdministration ma = new MedicationAdministration(); + ma.setMedication(new Reference(medId)); + IIdType moId = myMedicationAdministrationDao.create(ma).getId().toUnqualified(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(MedicationAdministration.SP_MEDICATION, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().add(new ReferenceParam("code", "04823543")))); + IBundleProvider results = myMedicationAdministrationDao.search(map); + List ids = toUnqualifiedIdValues(results); + + assertThat(ids, contains(moId.getValue())); + } + + @Test + public void testEmptyChain() { + + SearchParameterMap map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().add(new ReferenceParam("subject", "04823543").setChain("identifier")))); + IBundleProvider results = myMedicationAdministrationDao.search(map); + List ids = toUnqualifiedIdValues(results); + + assertThat(ids, empty()); + } + + @Test + public void testChainWithMultipleTypePossibilities() { + + Patient sub1 = new Patient(); + sub1.setActive(true); + sub1.addIdentifier().setSystem("foo").setValue("bar"); + String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue(); + + Group sub2 = new Group(); + sub2.setActive(true); + sub2.addIdentifier().setSystem("foo").setValue("bar"); + String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc1 = new Encounter(); + enc1.getSubject().setReference(sub1Id); + String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc2 = new Encounter(); + enc2.getSubject().setReference(sub2Id); + String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue(); + + List ids; + SearchParameterMap map; + IBundleProvider results; + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc1Id, enc2Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Patient", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc1Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Group", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc2Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "04823543").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, empty()); + } + + @Test + public void testEverythingTimings() throws Exception { + String methodName = "testEverythingTimings"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Medication med = new Medication(); + med.getCode().setText(methodName); + IIdType medId = myMedicationDao.create(med, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat = new Patient(); + pat.addAddress().addLine(methodName); + pat.getManagingOrganization().setReferenceElement(orgId); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat2.addAddress().addLine(methodName + "2"); + pat2.getManagingOrganization().setReferenceElement(orgId); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + MedicationRequest mo = new MedicationRequest(); + mo.getSubject().setReferenceElement(patId); + mo.setMedication(new Reference(medId)); + IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); + + HttpServletRequest request = mock(HttpServletRequest.class); + IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd); + assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2)); + + request = mock(HttpServletRequest.class); + resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd); + assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId)); + } + + /** + * Per message from David Hay on Skype + */ + @Test + public void testEverythingWithLargeSet() throws Exception { + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + String inputString = IOUtils.toString(getClass().getResourceAsStream("/david_big_bundle.json"), StandardCharsets.UTF_8); + Bundle inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputString); + inputBundle.setType(BundleType.TRANSACTION); + + Set allIds = new TreeSet(); + for (BundleEntryComponent nextEntry : inputBundle.getEntry()) { + nextEntry.getRequest().setMethod(HTTPVerb.PUT); + nextEntry.getRequest().setUrl(nextEntry.getResource().getId()); + allIds.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + + mySystemDao.transaction(mySrd, inputBundle); + + SearchParameterMap map = new SearchParameterMap(); + map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE); + IPrimitiveType count = new IntegerType(1000); + IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd); + + TreeSet ids = new TreeSet(toUnqualifiedVersionlessIdValues(everything)); + assertThat(ids, hasItem("List/A161444")); + assertThat(ids, hasItem("List/A161468")); + assertThat(ids, hasItem("List/A161500")); + + ourLog.info("Expected {} - {}", allIds.size(), allIds); + ourLog.info("Actual {} - {}", ids.size(), ids); + assertEquals(allIds, ids); + + ids = new TreeSet(); + for (int i = 0; i < everything.size(); i++) { + for (IBaseResource next : everything.getResources(i, i + 1)) { + ids.add(next.getIdElement().toUnqualifiedVersionless().getValue()); + } + } + assertThat(ids, hasItem("List/A161444")); + assertThat(ids, hasItem("List/A161468")); + assertThat(ids, hasItem("List/A161500")); + + ourLog.info("Expected {} - {}", allIds.size(), allIds); + ourLog.info("Actual {} - {}", ids.size(), ids); + assertEquals(allIds, ids); + + } + + @SuppressWarnings("unused") + @Test + public void testHasAndHas() { + Patient p1 = new Patient(); + p1.setActive(true); + IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.setActive(true); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + Observation p1o1 = new Observation(); + p1o1.setStatus(ObservationStatus.FINAL); + p1o1.getSubject().setReferenceElement(p1id); + IIdType p1o1id = myObservationDao.create(p1o1).getId().toUnqualifiedVersionless(); + + Observation p1o2 = new Observation(); + p1o2.setEffective(new DateTimeType("2001-01-01")); + p1o2.getSubject().setReferenceElement(p1id); + IIdType p1o2id = myObservationDao.create(p1o2).getId().toUnqualifiedVersionless(); + + Observation p2o1 = new Observation(); + p2o1.setStatus(ObservationStatus.FINAL); + p2o1.getSubject().setReferenceElement(p2id); + IIdType p2o1id = myObservationDao.create(p2o1).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + + HasAndListParam hasAnd = new HasAndListParam(); + hasAnd.addValue(new HasOrListParam().add(new HasParam("Observation", "subject", "status", "final"))); + hasAnd.addValue(new HasOrListParam().add(new HasParam("Observation", "subject", "date", "2001-01-01"))); + map.add("_has", hasAnd); + List actual = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(actual, containsInAnyOrder(p1id.getValue())); + + } + + @Test + public void testHasParameter() { + IIdType pid0; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("FOO"); + obs.getSubject().setReferenceElement(pid0); + myObservationDao.create(obs, mySrd); + } + { + Device device = new Device(); + device.addIdentifier().setValue("DEVICEID"); + IIdType devId = myDeviceDao.create(device, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("NOLINK"); + obs.setDevice(new Reference(devId)); + myObservationDao.create(obs, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); + + // No targets exist + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|UNKNOWN")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); + + // Target exists but doesn't link to us + params = new SearchParameterMap(); + params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|NOLINK")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); + } + + @Test + public void testHasParameterChained() { + IIdType pid0; + { + Device device = new Device(); + device.addIdentifier().setSystem("urn:system").setValue("DEVICEID"); + IIdType devId = myDeviceDao.create(device, mySrd).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setGender(AdministrativeGender.MALE); + pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("FOO"); + obs.setDevice(new Reference(devId)); + obs.setSubject(new Reference(pid0)); + myObservationDao.create(obs, mySrd).getId(); + } + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); + + // No targets exist + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|UNKNOWN")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); + + // Target exists but doesn't link to us + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|NOLINK")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); + } + + @Test + public void testHasParameterInvalidResourceType() { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation__", "subject", "identifier", "urn:system|FOO")); + try { + myPatientDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid resource type: Observation__", e.getMessage()); + } + } + + @Test + public void testHasParameterInvalidSearchParam() { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "IIIIDENFIEYR", "urn:system|FOO")); + try { + myPatientDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unknown parameter name: Observation:IIIIDENFIEYR", e.getMessage()); + } + } + + @Test + public void testHasParameterInvalidTargetPath() { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "soooooobject", "identifier", "urn:system|FOO")); + try { + myPatientDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unknown parameter name: Observation:soooooobject", e.getMessage()); + } + } + + @Test + public void testIndexNoDuplicatesDate() { + Encounter order = new Encounter(); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-12T11:12:12Z")).setEndElement(new DateTimeType("2011-12-12T11:12:12Z")); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-12T11:12:12Z")).setEndElement(new DateTimeType("2011-12-12T11:12:12Z")); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-12T11:12:12Z")).setEndElement(new DateTimeType("2011-12-12T11:12:12Z")); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-11T11:12:12Z")).setEndElement(new DateTimeType("2011-12-11T11:12:12Z")); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-11T11:12:12Z")).setEndElement(new DateTimeType("2011-12-11T11:12:12Z")); + order.addLocation().getPeriod().setStartElement(new DateTimeType("2011-12-11T11:12:12Z")).setEndElement(new DateTimeType("2011-12-11T11:12:12Z")); + + IIdType id = myEncounterDao.create(order, mySrd).getId().toUnqualifiedVersionless(); + + List actual = toUnqualifiedVersionlessIds( + myEncounterDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Encounter.SP_LOCATION_PERIOD, new DateParam("2011-12-12T11:12:12Z")))); + assertThat(actual, contains(id)); + + Class type = ResourceIndexedSearchParamDate.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i WHERE i.myMissing = false", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + } + + @Test + public void testIndexNoDuplicatesNumber() { + Immunization res = new Immunization(); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(2); + res.addVaccinationProtocol().setDoseSequence(2); + res.addVaccinationProtocol().setDoseSequence(2); + + IIdType id = myImmunizationDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + List actual = toUnqualifiedVersionlessIds(myImmunizationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Immunization.SP_DOSE_SEQUENCE, new NumberParam("1")))); + assertThat(actual, contains(id)); + + Class type = ResourceIndexedSearchParamNumber.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + } + + @Test + public void testIndexNoDuplicatesQuantity() { + Substance res = new Substance(); + res.addInstance().getQuantity().setSystem("http://foo").setCode("UNIT").setValue(123); + res.addInstance().getQuantity().setSystem("http://foo").setCode("UNIT").setValue(123); + res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232); + res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232); + + IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamQuantity.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds( + mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam((ParamPrefixEnum) null, 123, "http://foo", "UNIT")))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesReference() { + Practitioner pract = new Practitioner(); + pract.setId("Practitioner/somepract"); + pract.addName().setFamily("SOME PRACT"); + myPractitionerDao.update(pract, mySrd); + Practitioner pract2 = new Practitioner(); + pract2.setId("Practitioner/somepract2"); + pract2.addName().setFamily("SOME PRACT2"); + myPractitionerDao.update(pract2, mySrd); + + ProcedureRequest res = new ProcedureRequest(); + res.addReplaces(new Reference("Practitioner/somepract")); + res.addReplaces(new Reference("Practitioner/somepract")); + res.addReplaces(new Reference("Practitioner/somepract2")); + res.addReplaces(new Reference("Practitioner/somepract2")); + + IIdType id = myProcedureRequestDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + Class type = ResourceLink.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds( + myProcedureRequestDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ProcedureRequest.SP_REPLACES, new ReferenceParam("Practitioner/somepract")))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesString() { + Patient p = new Patient(); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamString.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i WHERE i.myMissing = false", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_ADDRESS, new StringParam("123 Fake Street")))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesToken() { + Patient res = new Patient(); + res.addIdentifier().setSystem("http://foo1").setValue("123"); + res.addIdentifier().setSystem("http://foo1").setValue("123"); + res.addIdentifier().setSystem("http://foo2").setValue("1234"); + res.addIdentifier().setSystem("http://foo2").setValue("1234"); + + IIdType id = myPatientDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamToken.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i WHERE i.myMissing = false", type).getResultList(); + ourLog.info(toStringMultiline(results)); + + // This is 3 for now because the FluentPath for Patient:deceased adds a value.. this should + // be corrected at some point, and we'll then drop back down to 2 + assertEquals(3, results.size()); + + List actual = toUnqualifiedVersionlessIds(myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_IDENTIFIER, new TokenParam("http://foo1", "123")))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesUri() { + ValueSet res = new ValueSet(); + res.getCompose().addInclude().setSystem("http://foo"); + res.getCompose().addInclude().setSystem("http://bar"); + res.getCompose().addInclude().setSystem("http://foo"); + res.getCompose().addInclude().setSystem("http://bar"); + res.getCompose().addInclude().setSystem("http://foo"); + res.getCompose().addInclude().setSystem("http://bar"); + + IIdType id = myValueSetDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamUri.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i WHERE i.myMissing = false", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_REFERENCE, new UriParam("http://foo")))); + assertThat(actual, contains(id)); + } + + /** + * #454 + */ + @Test + public void testIndexWithUtf8Chars() throws IOException { + String input = IOUtils.toString(getClass().getResourceAsStream("/bug454_utf8.json"), StandardCharsets.UTF_8); + + CodeSystem cs = (CodeSystem) myFhirCtx.newJsonParser().parseResource(input); + myCodeSystemDao.create(cs); + } + + @Test + public void testSearchAll() { + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + myPatientDao.create(patient, mySrd); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester").addGiven("John"); + myPatientDao.create(patient, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + List patients = toList(myPatientDao.search(params)); + assertTrue(patients.size() >= 2); + } + + @Test + public void testSearchByIdParam() { + String id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + String id2; + { + Organization patient = new Organization(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id2 = myOrganizationDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1)); + + params = new SearchParameterMap(); + params.add("_id", new StringParam(id1)); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1)); + + params = new SearchParameterMap(); + params.add("_id", new StringParam("9999999999999999")); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.add("_id", new StringParam(id2)); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + } + + @Test + public void testSearchByIdParamAnd() { + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + StringAndListParam param; + + params = new SearchParameterMap(); + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart()))); + param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart()))); + params.add("_id", param); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart()))); + param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart()))); + params.add("_id", param); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + + params = new SearchParameterMap(); + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart()))); + param.addAnd(new StringOrListParam().addOr(new StringParam("9999999999999"))); + params.add("_id", param); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + + params = new SearchParameterMap(); + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("9999999999999"))); + param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart()))); + params.add("_id", param); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + + } + + @Test + public void testSearchByIdParamOr() { + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + long betweenTime = System.currentTimeMillis(); + IIdType id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart()))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2)); + + params = new SearchParameterMap(); + params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id1.getIdPart()))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam("999999999999"))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + + // With lastupdated + + params = new SearchParameterMap(); + params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart()))); + params.setLastUpdated(new DateRangeParam(new Date(betweenTime), null)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2)); + + } + + @Test + public void testSearchByIdParamWrongType() { + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id2; + { + Organization patient = new Organization(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id2 = myOrganizationDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart()))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + + } + + @Test + public void testSearchCode() { + Subscription subs = new Subscription(); + subs.setStatus(SubscriptionStatus.ACTIVE); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?"); + IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + assertThat(toUnqualifiedVersionlessIds(mySubscriptionDao.search(params)), contains(id)); + + params = new SearchParameterMap(); + params.add(Subscription.SP_TYPE, new TokenParam(null, SubscriptionChannelType.WEBSOCKET.toCode())); + params.add(Subscription.SP_STATUS, new TokenParam(null, SubscriptionStatus.ACTIVE.toCode())); + assertThat(toUnqualifiedVersionlessIds(mySubscriptionDao.search(params)), contains(id)); + + params = new SearchParameterMap(); + params.add(Subscription.SP_TYPE, new TokenParam(null, SubscriptionChannelType.WEBSOCKET.toCode())); + params.add(Subscription.SP_STATUS, new TokenParam(null, SubscriptionStatus.ACTIVE.toCode() + "2")); + assertThat(toUnqualifiedVersionlessIds(mySubscriptionDao.search(params)), empty()); + + // Wrong param + params = new SearchParameterMap(); + params.add(Subscription.SP_STATUS, new TokenParam(null, SubscriptionChannelType.WEBSOCKET.toCode())); + assertThat(toUnqualifiedVersionlessIds(mySubscriptionDao.search(params)), empty()); + } + + @Test + public void testSearchCompositeParam() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamN01"); + o1.setValue(new StringType("testSearchCompositeParamS01")); + IIdType id1 = myObservationDao.create(o1, mySrd).getId(); + + Observation o2 = new Observation(); + o2.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamN01"); + o2.setValue(new StringType("testSearchCompositeParamS02")); + IIdType id2 = myObservationDao.create(o2, mySrd).getId(); + + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01"); + StringParam v1 = new StringParam("testSearchCompositeParamS01"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val)); + assertEquals(1, result.size().intValue()); + assertEquals(id1.toUnqualifiedVersionless(), result.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01"); + StringParam v1 = new StringParam("testSearchCompositeParamS02"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val)); + assertEquals(1, result.size().intValue()); + assertEquals(id2.toUnqualifiedVersionless(), result.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + } + } + + @Test + public void testSearchCompositeParamDate() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); + o1.setValue(new Period().setStartElement(new DateTimeType("2001-01-01T11:11:11Z")).setEndElement(new DateTimeType("2001-01-01T12:11:11Z"))); + IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); + o2.setValue(new Period().setStartElement(new DateTimeType("2001-01-02T11:11:11Z")).setEndElement(new DateTimeType("2001-01-02T12:11:11Z"))); + IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + DateParam v1 = new DateParam("2001-01-01"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_DATE, val)); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1)); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + DateParam v1 = new DateParam(">2001-01-01T10:12:12Z"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_DATE, val)); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + DateParam v1 = new DateParam("gt2001-01-01T11:12:12Z"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_DATE, val)); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + DateParam v1 = new DateParam("gt2001-01-01T15:12:12Z"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_DATE, val)); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2)); + } + + } + + @Test + public void testSearchCompositeParamQuantity() { + //@formatter:off + Observation o1 = new Observation(); + o1.addComponent() + .setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1"))) + .setValue(new Quantity().setSystem("http://bar").setCode("code1").setValue(100)); + o1.addComponent() + .setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2"))) + .setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(100)); + IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.addComponent() + .setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1"))) + .setValue(new Quantity().setSystem("http://bar").setCode("code1").setValue(200)); + o2.addComponent() + .setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code3"))) + .setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(200)); + IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + //@formatter:on + + String param = Observation.SP_COMPONENT_CODE_VALUE_QUANTITY; + + { + TokenParam v0 = new TokenParam("http://foo", "code1"); + QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val)); + assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue())); + } + { + TokenParam v0 = new TokenParam("http://foo", "code1"); + QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 50, "http://bar", "code1"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val)); + assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue(), id2.getValue())); + } + { + TokenParam v0 = new TokenParam("http://foo", "code4"); + QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 50, "http://bar", "code1"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val)); + assertThat(toUnqualifiedVersionlessIdValues(result), empty()); + } + { + TokenParam v0 = new TokenParam("http://foo", "code1"); + QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 50, "http://bar", "code4"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val)); + assertThat(toUnqualifiedVersionlessIdValues(result), empty()); + } + } + + @Test + public void testSearchDateWrongParam() { + Patient p1 = new Patient(); + p1.getBirthDateElement().setValueAsString("1980-01-01"); + String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue(); + + Patient p2 = new Patient(); + p2.setDeceased(new DateTimeType("1980-01-01")); + String id2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_BIRTHDATE, new DateParam("1980-01-01"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_DEATH_DATE, new DateParam("1980-01-01"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2)); + assertEquals(1, found.size().intValue()); + } + + } + + /** + * #222 + */ + @Test + public void testSearchForDeleted() { + + { + Patient patient = new Patient(); + patient.setId("TEST"); + patient.setLanguageElement(new CodeType("TEST")); + patient.addName().setFamily("TEST"); + patient.addIdentifier().setSystem("TEST").setValue("TEST"); + myPatientDao.update(patient, mySrd); + } + + SearchParameterMap params; + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_id", new StringParam("TEST")); + assertEquals(1, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_language", new StringParam("TEST")); + assertEquals(1, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_IDENTIFIER, new TokenParam("TEST", "TEST")); + assertEquals(1, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_NAME, new StringParam("TEST")); + assertEquals(1, toList(myPatientDao.search(params)).size()); + + myPatientDao.delete(new IdType("Patient/TEST"), mySrd); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_id", new StringParam("TEST")); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_language", new StringParam("TEST")); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_IDENTIFIER, new TokenParam("TEST", "TEST")); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_NAME, new StringParam("TEST")); + assertEquals(0, toList(myPatientDao.search(params)).size()); + + } + + @Test + public void testSearchForUnknownAlphanumericId() { + { + SearchParameterMap map = new SearchParameterMap(); + map.add("_id", new StringParam("testSearchForUnknownAlphanumericId")); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(0, retrieved.size().intValue()); + } + } + + @Test + public void testSearchLanguageParam() { + IIdType id1; + { + Patient patient = new Patient(); + patient.getLanguageElement().setValue("en_CA"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("testSearchLanguageParam").addGiven("Joe"); + id1 = myPatientDao.create(patient, mySrd).getId(); + } + IIdType id2; + { + Patient patient = new Patient(); + patient.getLanguageElement().setValue("en_US"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("testSearchLanguageParam").addGiven("John"); + id2 = myPatientDao.create(patient, mySrd).getId(); + } + SearchParameterMap params; + { + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + + params.add(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_CA")); + List patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + assertEquals(id1.toUnqualifiedVersionless(), patients.get(0).getIdElement().toUnqualifiedVersionless()); + } + { + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + + params.add(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_US")); + List patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + assertEquals(id2.toUnqualifiedVersionless(), patients.get(0).getIdElement().toUnqualifiedVersionless()); + } + { + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + + params.add(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_GB")); + List patients = toList(myPatientDao.search(params)); + assertEquals(0, patients.size()); + } + } + + @Test + public void testSearchLanguageParamAndOr() { + IIdType id1; + { + Patient patient = new Patient(); + patient.getLanguageElement().setValue("en_CA"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("testSearchLanguageParam").addGiven("Joe"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + Date betweenTime = new Date(); + + IIdType id2; + { + Patient patient = new Patient(); + patient.getLanguageElement().setValue("en_US"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("testSearchLanguageParam").addGiven("John"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); + params.setLastUpdated(new DateRangeParam(betweenTime, null)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + { + SearchParameterMap params = new SearchParameterMap(); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA"))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + { + SearchParameterMap params = new SearchParameterMap(); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ"))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + } + { + SearchParameterMap params = new SearchParameterMap(); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + } + { + SearchParameterMap params = new SearchParameterMap(); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add("_id", new StringParam(id1.getIdPart())); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + { + SearchParameterMap params = new SearchParameterMap(); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); + params.add(IAnyResource.SP_RES_LANGUAGE, and); + params.add("_id", new StringParam(id1.getIdPart())); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + + } + + @Test + public void testSearchLastUpdatedParam() throws InterruptedException { + String methodName = "testSearchLastUpdatedParam"; + + int sleep = 100; + Thread.sleep(sleep); + + DateTimeType beforeAny = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI); + IIdType id1a; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily(methodName).addGiven("Joe"); + id1a = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id1b; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily(methodName + "XXXX").addGiven("Joe"); + id1b = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + Thread.sleep(1100); + DateTimeType beforeR2 = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI); + Thread.sleep(1100); + + IIdType id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily(methodName).addGiven("John"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + SearchParameterMap params = new SearchParameterMap(); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, hasItems(id1a, id1b, id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(beforeAny, null)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, hasItems(id1a, id1b, id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(beforeR2, null)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, hasItems(id2)); + assertThat(patients, not(hasItems(id1a, id1b))); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(beforeAny, beforeR2)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients.toString(), patients, not(hasItems(id2))); + assertThat(patients.toString(), patients, (hasItems(id1a, id1b))); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(null, beforeR2)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, (hasItems(id1a, id1b))); + assertThat(patients, not(hasItems(id2))); + } + + + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, beforeR2))); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(hasItems(id1a, id1b))); + assertThat(patients, (hasItems(id2))); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, beforeR2))); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, (hasItems(id1a, id1b))); + assertThat(patients, not(hasItems(id2))); + } + + } + + @SuppressWarnings("deprecation") + @Test + public void testSearchLastUpdatedParamWithComparator() throws InterruptedException { + IIdType id0; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + int sleep = 100; + + long start = System.currentTimeMillis(); + Thread.sleep(sleep); + + IIdType id1a; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1a = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id1b; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1b = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + ourLog.info("Res 1: {}", myPatientDao.read(id0, mySrd).getMeta().getLastUpdatedElement().getValueAsString()); + ourLog.info("Res 2: {}", myPatientDao.read(id1a, mySrd).getMeta().getLastUpdatedElement().getValueAsString()); + ourLog.info("Res 3: {}", myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValueAsString()); + + Thread.sleep(sleep); + long end = System.currentTimeMillis(); + + SearchParameterMap map; + Date startDate = new Date(start); + Date endDate = new Date(end); + DateTimeType startDateTime = new DateTimeType(startDate, TemporalPrecisionEnum.MILLI); + DateTimeType endDateTime = new DateTimeType(endDate, TemporalPrecisionEnum.MILLI); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(startDateTime, endDateTime)); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, startDateTime), new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, endDateTime))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime), new DateParam(ParamPrefixEnum.LESSTHAN, endDateTime))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime.getValue()), + new DateParam(ParamPrefixEnum.LESSTHAN, myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValue()))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a)); + } + + @Test + public void testSearchNameParam() { + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv"); + id1 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("testSearchNameParam02Fam").addGiven("testSearchNameParam02Giv"); + myPatientDao.create(patient, mySrd); + } + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam")); + List patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + assertEquals(id1.getIdPart(), patients.get(0).getIdElement().getIdPart()); + + // Given name shouldn't return for family param + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Giv")); + patients = toList(myPatientDao.search(params)); + assertEquals(0, patients.size()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_NAME, new StringParam("testSearchNameParam01Fam")); + patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + assertEquals(id1.getIdPart(), patients.get(0).getIdElement().getIdPart()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_NAME, new StringParam("testSearchNameParam01Giv")); + patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + assertEquals(id1.getIdPart(), patients.get(0).getIdElement().getIdPart()); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Foo")); + patients = toList(myPatientDao.search(params)); + assertEquals(0, patients.size()); + + } + + /** + * TODO: currently this doesn't index, we should get it working + */ + @Test + public void testSearchNearParam() { + { + Location loc = new Location(); + loc.getPosition().setLatitude(43.7); + loc.getPosition().setLatitude(79.4); + myLocationDao.create(loc, mySrd); + } + } + + @Test + public void testSearchNumberParam() { + Encounter e1 = new Encounter(); + e1.addIdentifier().setSystem("foo").setValue("testSearchNumberParam01"); + e1.getLength().setSystem(BaseHapiFhirDao.UCUM_NS).setCode("min").setValue(4.0 * 24 * 60); + IIdType id1 = myEncounterDao.create(e1, mySrd).getId(); + + Encounter e2 = new Encounter(); + e2.addIdentifier().setSystem("foo").setValue("testSearchNumberParam02"); + e2.getLength().setSystem(BaseHapiFhirDao.UCUM_NS).setCode("year").setValue(2.0); + IIdType id2 = myEncounterDao.create(e2, mySrd).getId(); + { + IBundleProvider found = myEncounterDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Encounter.SP_LENGTH, new NumberParam(">2"))); + assertEquals(2, found.size().intValue()); + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1.toUnqualifiedVersionless(), id2.toUnqualifiedVersionless())); + } + { + IBundleProvider found = myEncounterDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Encounter.SP_LENGTH, new NumberParam("<1"))); + assertEquals(0, found.size().intValue()); + } + { + IBundleProvider found = myEncounterDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Encounter.SP_LENGTH, new NumberParam("4"))); + assertEquals(1, found.size().intValue()); + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1.toUnqualifiedVersionless())); + } + } + + @Test + public void testSearchNumberWrongParam() { + ImmunizationRecommendation ir1 = new ImmunizationRecommendation(); + ir1.addRecommendation().setDoseNumber(1); + String id1 = myImmunizationRecommendationDao.create(ir1).getId().toUnqualifiedVersionless().getValue(); + + ImmunizationRecommendation ir2 = new ImmunizationRecommendation(); + ir2.addRecommendation().setDoseNumber(2); + String id2 = myImmunizationRecommendationDao.create(ir2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myImmunizationRecommendationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ImmunizationRecommendation.SP_DOSE_NUMBER, new NumberParam("1"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myImmunizationRecommendationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ImmunizationRecommendation.SP_DOSE_SEQUENCE, new NumberParam("1"))); + assertThat(toUnqualifiedVersionlessIdValues(found), empty()); + assertEquals(0, found.size().intValue()); + } + + } + + /** + * When a valueset expansion returns no codes + */ + @Test + public void testSearchOnCodesWithNone() { + ValueSet vs = new ValueSet(); + vs.setUrl("urn:testSearchOnCodesWithNone"); + myValueSetDao.create(vs); + + Patient p1 = new Patient(); + p1.setGender(AdministrativeGender.MALE); + String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue(); + + Patient p2 = new Patient(); + p2.setGender(AdministrativeGender.FEMALE); + String id2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myPatientDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_GENDER, new TokenParam().setModifier(TokenParamModifier.IN).setValue("urn:testSearchOnCodesWithNone"))); + assertThat(toUnqualifiedVersionlessIdValues(found), empty()); + assertEquals(0, found.size().intValue()); + } + + } + + @Test + public void testSearchPagesExpiryDisabled() throws Exception { + IIdType pid1; + IIdType pid2; + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Date between = new Date(); + Thread.sleep(10); + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("EXPIRE")); + IBundleProvider bundleProvider = myPatientDao.search(params); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + + myDaoConfig.setExpireSearchResults(false); + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + Thread.sleep(1500); + + assertThat(toUnqualifiedVersionlessIds(bundleProvider), (containsInAnyOrder(pid1, pid2))); + } + + @Test + public void testSearchParamChangesType() { + String name = "testSearchParamChangesType"; + IIdType id; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam(name)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, contains(id)); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem(name).setValue(name); + patient.setId(id); + myPatientDao.update(patient, mySrd); + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam(name)); + patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(contains(id))); + + } + + @Test + public void testSearchPractitionerPhoneAndEmailParam() { + String methodName = "testSearchPractitionerPhoneAndEmailParam"; + IIdType id1; + { + Practitioner patient = new Practitioner(); + patient.addName().setFamily(methodName); + patient.addTelecom().setSystem(ContactPointSystem.PHONE).setValue("123"); + id1 = myPractitionerDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id2; + { + Practitioner patient = new Practitioner(); + patient.addName().setFamily(methodName); + patient.addTelecom().setSystem(ContactPointSystem.EMAIL).setValue("abc"); + id2 = myPractitionerDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + List patients; + + params = new SearchParameterMap(); + params.add(Practitioner.SP_FAMILY, new StringParam(methodName)); + params.add(Practitioner.SP_EMAIL, new TokenParam(null, "123")); + patients = toUnqualifiedVersionlessIds(myPractitionerDao.search(params)); + assertEquals(0, patients.size()); + + params = new SearchParameterMap(); + params.add(Practitioner.SP_FAMILY, new StringParam(methodName)); + patients = toUnqualifiedVersionlessIds(myPractitionerDao.search(params)); + assertEquals(2, patients.size()); + assertThat(patients, containsInAnyOrder(id1, id2)); + + params = new SearchParameterMap(); + params.add(Practitioner.SP_FAMILY, new StringParam(methodName)); + params.add(Practitioner.SP_EMAIL, new TokenParam(null, "abc")); + patients = toUnqualifiedVersionlessIds(myPractitionerDao.search(params)); + assertEquals(1, patients.size()); + assertThat(patients, containsInAnyOrder(id2)); + + params = new SearchParameterMap(); + params.add(Practitioner.SP_FAMILY, new StringParam(methodName)); + params.add(Practitioner.SP_PHONE, new TokenParam(null, "123")); + patients = toUnqualifiedVersionlessIds(myPractitionerDao.search(params)); + assertEquals(1, patients.size()); + assertThat(patients, containsInAnyOrder(id1)); + + } + + @Test + public void testSearchQuantityWrongParam() throws Exception { + Condition c1 = new Condition(); + c1.setAbatement(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L))); + String id1 = myConditionDao.create(c1).getId().toUnqualifiedVersionless().getValue(); + + Condition c2 = new Condition(); + c2.setOnset(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L))); + String id2 = myConditionDao.create(c2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myConditionDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ABATEMENT_AGE, new QuantityParam("1"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myConditionDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ONSET_AGE, new QuantityParam("1"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2)); + assertEquals(1, found.size().intValue()); + } + + } + + @Test + public void testSearchResourceLinkWithChain() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain01"); + IIdType patientId01 = myPatientDao.create(patient, mySrd).getId(); + + Patient patient02 = new Patient(); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain02"); + IIdType patientId02 = myPatientDao.create(patient02, mySrd).getId(); + + Observation obs01 = new Observation(); + obs01.setEffective(new DateTimeType(new Date())); + obs01.setSubject(new Reference(patientId01)); + IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId(); + + Observation obs02 = new Observation(); + obs02.setEffective(new DateTimeType(new Date())); + obs02.setSubject(new Reference(patientId02)); + IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId(); + + // Create another type, that shouldn't be returned + DiagnosticReport dr01 = new DiagnosticReport(); + dr01.setSubject(new Reference(patientId01)); + IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); + + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[] { patientId01, patientId02, obsId01, obsId02, drId01 }); + + List result = toList(myObservationDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01")))); + assertEquals(1, result.size()); + assertEquals(obsId01.getIdPart(), result.get(0).getIdElement().getIdPart()); + + result = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart())))); + assertEquals(1, result.size()); + + result = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart())))); + assertEquals(1, result.size()); + + result = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "999999999999")))); + assertEquals(0, result.size()); + + result = toList(myObservationDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChainXX")))); + assertEquals(2, result.size()); + + result = toList( + myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "testSearchResourceLinkWithChainXX")))); + assertEquals(2, result.size()); + + result = toList( + myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "|testSearchResourceLinkWithChainXX")))); + assertEquals(0, result.size()); + + } + + @Test + public void testSearchResourceLinkWithChainDouble() { + String methodName = "testSearchResourceLinkWithChainDouble"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId01 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Location locParent = new Location(); + locParent.setManagingOrganization(new Reference(orgId01)); + IIdType locParentId = myLocationDao.create(locParent, mySrd).getId().toUnqualifiedVersionless(); + + Location locChild = new Location(); + locChild.setPartOf(new Reference(locParentId)); + IIdType locChildId = myLocationDao.create(locChild, mySrd).getId().toUnqualifiedVersionless(); + + Location locGrandchild = new Location(); + locGrandchild.setPartOf(new Reference(locChildId)); + IIdType locGrandchildId = myLocationDao.create(locGrandchild, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider found; + ReferenceParam param; + + found = myLocationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("organization", new ReferenceParam(orgId01.getIdPart()))); + assertEquals(1, found.size().intValue()); + assertEquals(locParentId, found.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + + param = new ReferenceParam(orgId01.getIdPart()); + param.setChain("organization"); + found = myLocationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("partof", param)); + assertEquals(1, found.size().intValue()); + assertEquals(locChildId, found.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + + param = new ReferenceParam(orgId01.getIdPart()); + param.setChain("partof.organization"); + found = myLocationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("partof", param)); + assertEquals(1, found.size().intValue()); + assertEquals(locGrandchildId, found.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + + param = new ReferenceParam(methodName); + param.setChain("partof.organization.name"); + found = myLocationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("partof", param)); + assertEquals(1, found.size().intValue()); + assertEquals(locGrandchildId, found.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + } + + @Test + public void testSearchResourceLinkWithChainWithMultipleTypes() throws Exception { + Patient patient = new Patient(); + patient.addName().setFamily("testSearchResourceLinkWithChainWithMultipleTypes01"); + patient.addName().setFamily("testSearchResourceLinkWithChainWithMultipleTypesXX"); + IIdType patientId01 = myPatientDao.create(patient, mySrd).getId(); + + Location loc01 = new Location(); + loc01.getNameElement().setValue("testSearchResourceLinkWithChainWithMultipleTypes01"); + IIdType locId01 = myLocationDao.create(loc01, mySrd).getId(); + + Observation obs01 = new Observation(); + obs01.setEffective(new DateTimeType(new Date())); + obs01.setSubject(new Reference(patientId01)); + IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless(); + + Date between = new Date(); + Thread.sleep(10); + + Observation obs02 = new Observation(); + obs02.setEffective(new DateTimeType(new Date())); + obs02.setSubject(new Reference(locId01)); + IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(10); + Date after = new Date(); + + ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 }); + + List result; + SearchParameterMap params; + + result = toUnqualifiedVersionlessIds(myObservationDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesXX")))); + assertThat(result, containsInAnyOrder(obsId01)); + assertEquals(1, result.size()); + + result = toUnqualifiedVersionlessIds(myObservationDao.search( + new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("Patient", Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")))); + assertThat(result, containsInAnyOrder(obsId01)); + assertEquals(1, result.size()); + + params = new SearchParameterMap(); + params.add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")); + result = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertEquals(2, result.size()); + assertThat(result, containsInAnyOrder(obsId01, obsId02)); + + params = new SearchParameterMap(); + params.add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")); + params.setLastUpdated(new DateRangeParam(between, after)); + result = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertEquals(1, result.size()); + assertThat(result, containsInAnyOrder(obsId02)); + + result = toUnqualifiedVersionlessIds(myObservationDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesYY")))); + assertEquals(0, result.size()); + + } + + @Test + public void testSearchResourceLinkWithTextLogicalId() { + Patient patient = new Patient(); + patient.setId("testSearchResourceLinkWithTextLogicalId01"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalId01"); + IIdType patientId01 = myPatientDao.update(patient, mySrd).getId(); + + Patient patient02 = new Patient(); + patient02.setId("testSearchResourceLinkWithTextLogicalId02"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalId02"); + IIdType patientId02 = myPatientDao.update(patient02, mySrd).getId(); + + Observation obs01 = new Observation(); + obs01.setEffective(new DateTimeType(new Date())); + obs01.setSubject(new Reference(patientId01)); + IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId(); + + Observation obs02 = new Observation(); + obs02.setEffective(new DateTimeType(new Date())); + obs02.setSubject(new Reference(patientId02)); + IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId(); + + // Create another type, that shouldn't be returned + DiagnosticReport dr01 = new DiagnosticReport(); + dr01.setSubject(new Reference(patientId01)); + IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); + + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[] { patientId01, patientId02, obsId01, obsId02, drId01 }); + + List result = toList( + myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId01")))); + assertEquals(1, result.size()); + assertEquals(obsId01.getIdPart(), result.get(0).getIdElement().getIdPart()); + + result = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId99")))); + assertEquals(0, result.size()); + + result = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("999999999999999")))); + assertEquals(0, result.size()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchResourceReferenceMissingChain() { + IIdType oid1; + { + Organization org = new Organization(); + org.setActive(true); + oid1 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid1; + { + Task task = new Task(); + task.getRequester().setOnBehalfOf(new Reference(oid1)); + tid1 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid2; + { + Task task = new Task(); + task.setOwner(new Reference(oid1)); + tid2 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + + IIdType oid2; + { + Organization org = new Organization(); + org.setActive(true); + org.setName("NAME"); + oid2 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid3; + { + Task task = new Task(); + task.getRequester().setOnBehalfOf(new Reference(oid2)); + tid3 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.add(Organization.SP_NAME, new StringParam().setMissing(true)); + ids = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); + assertThat(ids, contains(oid1)); + + ourLog.info("Starting Search 2"); + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); + ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); + assertThat(ids, contains(tid1)); // NOT tid2 + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "false")); + ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); + assertThat(ids, contains(tid3)); + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); + ids = toUnqualifiedVersionlessIds(myPatientDao.search(map)); + assertThat(ids, empty()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchResourceReferenceOnlyCorrectPath() { + IIdType oid1; + { + Organization org = new Organization(); + org.setActive(true); + oid1 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid1; + { + Task task = new Task(); + task.getRequester().setOnBehalfOf(new Reference(oid1)); + tid1 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid2; + { + Task task = new Task(); + task.setOwner(new Reference(oid1)); + tid2 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam(oid1.getValue())); + ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); + assertThat(ids, contains(tid1)); // NOT tid2 + + } + + @Test + public void testSearchStringParamDoesntMatchWrongType() throws Exception { + IIdType pid1; + IIdType pid2; + { + Patient patient = new Patient(); + patient.addName().setFamily("HELLO"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Practitioner patient = new Practitioner(); + patient.addName().setFamily("HELLO"); + pid2 = myPractitionerDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + List patients; + + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("HELLO")); + patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInAnyOrder(pid1)); + assertThat(patients, not(containsInAnyOrder(pid2))); + } + + @Test + public void testSearchWithFetchSizeDefaultMaximum() { + myDaoConfig.setFetchSizeDefaultMaximum(5); + + for (int i = 0; i < 10; i++) { + Patient p = new Patient(); + p.addName().setFamily("PT" + i); + myPatientDao.create(p); + } + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + IBundleProvider values = myPatientDao.search(map); + assertEquals(5, values.size().intValue()); + assertEquals(5, values.getResources(0, 1000).size()); + } + + @Test + public void testSearchStringParam() throws Exception { + IIdType pid1; + IIdType pid2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Date between = new Date(); + Thread.sleep(10); + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + Date after = new Date(); + + SearchParameterMap params; + List patients; + + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_testSearchStringParam")); + params.setLastUpdated(new DateRangeParam(between, after)); + patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInAnyOrder(pid2)); + + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_testSearchStringParam")); + patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInAnyOrder(pid1, pid2)); + assertEquals(2, patients.size()); + + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("FOO_testSearchStringParam")); + patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertEquals(0, patients.size()); + + } + + @Test + public void testSearchStringParamReallyLong() { + String methodName = "testSearchStringParamReallyLong"; + String value = StringUtils.rightPad(methodName, 200, 'a'); + + IIdType longId; + IIdType shortId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily(value); + longId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + shortId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + + String substring = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + params.add(Patient.SP_FAMILY, new StringParam(substring)); + IBundleProvider found = myPatientDao.search(params); + assertEquals(1, toList(found).size()); + assertThat(toUnqualifiedVersionlessIds(found), contains(longId)); + assertThat(toUnqualifiedVersionlessIds(found), not(contains(shortId))); + + } + + @Test + public void testSearchStringParamWithNonNormalized() { + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_h\u00F6ra"); + myPatientDao.create(patient, mySrd); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_HORA"); + myPatientDao.create(patient, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_GIVEN, new StringParam("testSearchStringParamWithNonNormalized_hora")); + List patients = toList(myPatientDao.search(params)); + assertEquals(2, patients.size()); + + StringParam parameter = new StringParam("testSearchStringParamWithNonNormalized_hora"); + parameter.setExact(true); + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_GIVEN, parameter); + patients = toList(myPatientDao.search(params)); + assertEquals(0, patients.size()); + + } + + @Test + public void testSearchStringWrongParam() throws Exception { + Patient p1 = new Patient(); + p1.getNameFirstRep().setFamily("AAA"); + String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue(); + + Patient p2 = new Patient(); + p2.getNameFirstRep().addGiven("AAA"); + String id2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_FAMILY, new StringParam("AAA"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_GIVEN, new StringParam("AAA"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2)); + assertEquals(1, found.size().intValue()); + } + + } + + @Test + public void testSearchTokenParam() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1"); + patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem") + .setDisplay("testSearchTokenParamDisplay"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + myPatientDao.create(patient, mySrd); + + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testSearchTokenParam001")); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(1, retrieved.size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam(null, "testSearchTokenParam001")); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(1, retrieved.size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam("testSearchTokenParamSystem", "testSearchTokenParamCode")); + assertEquals(1, myPatientDao.search(map).size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); + assertEquals(0, myPatientDao.search(map).size().intValue()); + } + { + // Complete match + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamComText", true)); + assertEquals(1, myPatientDao.search(map).size().intValue()); + } + { + // Left match + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamcomtex", true)); + assertEquals(1, myPatientDao.search(map).size().intValue()); + } + { + // Right match + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamComTex", true)); + assertEquals(1, myPatientDao.search(map).size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + TokenOrListParam listParam = new TokenOrListParam(); + listParam.add("urn:system", "testSearchTokenParam001"); + listParam.add("urn:system", "testSearchTokenParam002"); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(2, retrieved.size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + TokenOrListParam listParam = new TokenOrListParam(); + listParam.add(null, "testSearchTokenParam001"); + listParam.add("urn:system", "testSearchTokenParam002"); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(2, retrieved.size().intValue()); + } + } + + @Test + public void testSearchTokenParamNoValue() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1"); + patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem") + .setDisplay("testSearchTokenParamDisplay"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + myPatientDao.create(patient, mySrd); + + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", null)); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(2, retrieved.size().intValue()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "")); + IBundleProvider retrieved = myPatientDao.search(map); + assertEquals(2, retrieved.size().intValue()); + } + } + + @Test + public void testSearchTokenWrongParam() throws Exception { + Patient p1 = new Patient(); + p1.setGender(AdministrativeGender.MALE); + String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue(); + + Patient p2 = new Patient(); + p2.addIdentifier().setValue(AdministrativeGender.MALE.toCode()); + String id2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_GENDER, new TokenParam(null, "male"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_IDENTIFIER, new TokenParam(null, "male"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2)); + assertEquals(1, found.size().intValue()); + } + + } + + @Test + @Ignore + public void testSearchUnknownContentParam() { + SearchParameterMap params = new SearchParameterMap(); + params.add(Constants.PARAM_CONTENT, new StringParam("fulltext")); + try { + myPatientDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _content", e.getMessage()); + } + } + + @Test + @Ignore + public void testSearchUnknownTextParam() { + SearchParameterMap params = new SearchParameterMap(); + params.add(Constants.PARAM_TEXT, new StringParam("fulltext")); + try { + myPatientDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _text", e.getMessage()); + } + } + + @Test + public void testSearchUriWrongParam() throws Exception { + ValueSet v1 = new ValueSet(); + v1.getUrlElement().setValue("http://foo"); + String id1 = myValueSetDao.create(v1).getId().toUnqualifiedVersionless().getValue(); + + ValueSet v2 = new ValueSet(); + v2.getExpansion().getIdentifierElement().setValue("http://foo"); + String id2 = myValueSetDao.create(v2).getId().toUnqualifiedVersionless().getValue(); + + { + IBundleProvider found = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://foo"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1)); + assertEquals(1, found.size().intValue()); + } + { + IBundleProvider found = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_EXPANSION, new UriParam("http://foo"))); + assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2)); + assertEquals(1, found.size().intValue()); + } + + } + + @Test + public void testSearchValueQuantity() { + String methodName = "testSearchValueQuantity"; + + String id1; + { + Observation o = new Observation(); + o.getCode().addCoding().setSystem("urn:foo").setCode(methodName + "code"); + Quantity q = new Quantity().setSystem("urn:bar:" + methodName).setCode(methodName + "units").setValue(100); + o.setValue(q); + id1 = myObservationDao.create(o, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + + String id2; + { + Observation o = new Observation(); + o.getCode().addCoding().setSystem("urn:foo").setCode(methodName + "code"); + Quantity q = new Quantity().setSystem("urn:bar:" + methodName).setCode(methodName + "units").setValue(5); + o.setValue(q); + id2 = myObservationDao.create(o, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + + SearchParameterMap map; + IBundleProvider found; + QuantityParam param; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); + map.add(Observation.SP_VALUE_QUANTITY, param); + found = myObservationDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(found), contains(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, methodName + "units"); + map.add(Observation.SP_VALUE_QUANTITY, param); + found = myObservationDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(found), contains(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, null); + map.add(Observation.SP_VALUE_QUANTITY, param); + found = myObservationDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(found), contains(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, methodName + "units"); + map.add(Observation.SP_VALUE_QUANTITY, param); + found = myObservationDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(found), contains(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("1000"), "urn:bar:" + methodName, methodName + "units"); + map.add(Observation.SP_VALUE_QUANTITY, param); + found = myObservationDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(found), empty()); + + } + + @Test + public void testSearchWithDate() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); + IIdType id2; + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_BIRTHDATE, new DateParam("2011-01-01")); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, contains(id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_BIRTHDATE, new DateParam("2011-01-03")); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, empty()); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_BIRTHDATE, new DateParam("2011-01-03").setPrefix(ParamPrefixEnum.LESSTHAN)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, contains(id2)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_BIRTHDATE, new DateParam("2010-01-01").setPrefix(ParamPrefixEnum.LESSTHAN)); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, empty()); + } + } + + @Test + public void testSearchWithEmptySort() { + SearchParameterMap criteriaUrl = new SearchParameterMap(); + DateRangeParam range = new DateRangeParam(); + range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, 1000000)); + range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, 2000000)); + criteriaUrl.setLastUpdated(range); + criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC)); + IBundleProvider results = myObservationDao.search(criteriaUrl); + assertEquals(0, results.size().intValue()); + } + + @Test + public void testSearchWithIncludes() { + String methodName = "testSearchWithIncludes"; + IIdType parentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + parentOrgId = myOrganizationDao.create(org, mySrd).getId(); + } + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1"); + org.setPartOf(new Reference(parentOrgId)); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId(); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + myPatientDao.create(patient, mySrd); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_" + methodName + "_P2").addGiven("John"); + myPatientDao.create(patient, mySrd); + } + + { + // No includes + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + List patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + } + { + // Named include + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(Patient.INCLUDE_ORGANIZATION.asNonRecursive()); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(2, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + } + { + // Named include with parent non-recursive + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(Patient.INCLUDE_ORGANIZATION); + params.addInclude(Organization.INCLUDE_PARTOF.asNonRecursive()); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(2, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + } + { + // Named include with parent recursive + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(Patient.INCLUDE_ORGANIZATION); + params.addInclude(Organization.INCLUDE_PARTOF.asRecursive()); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(3, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + assertEquals(Organization.class, patients.get(2).getClass()); + } + { + // * include non recursive + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(IBaseResource.INCLUDE_ALL.asNonRecursive()); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(2, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + } + { + // * include recursive + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(IBaseResource.INCLUDE_ALL.asRecursive()); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(3, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + assertEquals(Organization.class, patients.get(2).getClass()); + } + { + // Irrelevant include + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(Encounter.INCLUDE_EPISODEOFCARE); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(1, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + } + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithIncludesParameterNoRecurse() { + String methodName = "testSearchWithIncludes"; + IIdType parentParentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + parentParentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType parentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + org.setPartOf(new Reference(parentParentOrgId)); + parentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType orgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1"); + org.setPartOf(new Reference(parentOrgId)); + orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType patientId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + patientId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + SearchParameterMap params = new SearchParameterMap(); + params.add(IAnyResource.SP_RES_ID, new StringParam(orgId.getIdPart())); + params.addInclude(Organization.INCLUDE_PARTOF.asNonRecursive()); + List resources = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(resources, contains(orgId, parentOrgId)); + } + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithIncludesParameterRecurse() { + String methodName = "testSearchWithIncludes"; + IIdType parentParentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + parentParentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType parentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + org.setPartOf(new Reference(parentParentOrgId)); + parentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType orgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1"); + org.setPartOf(new Reference(parentOrgId)); + orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType patientId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + patientId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + SearchParameterMap params = new SearchParameterMap(); + params.add(IAnyResource.SP_RES_ID, new StringParam(orgId.getIdPart())); + params.addInclude(Organization.INCLUDE_PARTOF.asRecursive()); + List resources = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + ourLog.info(resources.toString()); + assertThat(resources, contains(orgId, parentOrgId, parentParentOrgId)); + } + } + + @Test + public void testSearchWithIncludesStarNoRecurse() { + String methodName = "testSearchWithIncludes"; + IIdType parentParentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + parentParentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType parentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + org.setPartOf(new Reference(parentParentOrgId)); + parentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType orgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1"); + org.setPartOf(new Reference(parentOrgId)); + orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType patientId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + patientId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(new Include("*").asNonRecursive()); + List resources = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(resources, contains(patientId, orgId)); + } + } + + @Test + public void testSearchWithIncludesStarRecurse() { + String methodName = "testSearchWithIncludes"; + IIdType parentParentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + parentParentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType parentOrgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1Parent"); + org.setPartOf(new Reference(parentParentOrgId)); + parentOrgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType orgId; + { + Organization org = new Organization(); + org.getNameElement().setValue(methodName + "_O1"); + org.setPartOf(new Reference(parentOrgId)); + orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType patientId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + patientId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1")); + params.addInclude(new Include("*").asRecursive()); + List resources = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(resources, contains(patientId, orgId, parentOrgId, parentParentOrgId)); + } + } + + /** + * Test for #62 + */ + @Test + public void testSearchWithIncludesThatHaveTextId() { + { + Organization org = new Organization(); + org.setId("testSearchWithIncludesThatHaveTextIdid1"); + org.getNameElement().setValue("testSearchWithIncludesThatHaveTextId_O1"); + IIdType orgId = myOrganizationDao.update(org, mySrd).getId(); + assertThat(orgId.getValue(), endsWith("Organization/testSearchWithIncludesThatHaveTextIdid1/_history/1")); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_testSearchWithIncludesThatHaveTextId_P1").addGiven("Joe"); + patient.getManagingOrganization().setReferenceElement(orgId); + myPatientDao.create(patient, mySrd); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchWithIncludesThatHaveTextId_P2").addGiven("John"); + myPatientDao.create(patient, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_testSearchWithIncludesThatHaveTextId_P1")); + params.addInclude(Patient.INCLUDE_ORGANIZATION); + IBundleProvider search = myPatientDao.search(params); + List patients = toList(search); + assertEquals(2, patients.size()); + assertEquals(Patient.class, patients.get(0).getClass()); + assertEquals(Organization.class, patients.get(1).getClass()); + + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("Tester_testSearchWithIncludesThatHaveTextId_P1")); + patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + + } + + @Test + public void testSearchWithMissingDate() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // Date Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + DateParam param = new DateParam(); + param.setMissing(false); + params.add(Patient.SP_BIRTHDATE, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(notMissing)); + assertThat(patients, not(containsInRelativeOrder(missing))); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + DateParam param = new DateParam(); + param.setMissing(true); + params.add(Patient.SP_BIRTHDATE, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithMissingDate2() { + MedicationRequest mr1 = new MedicationRequest(); + mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); + IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); + + MedicationRequest mr2 = new MedicationRequest(); + mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(MedicationRequest.SP_DATE, new DateParam().setMissing(true)); + IBundleProvider results = myMedicationRequestDao.search(map); + List ids = toUnqualifiedVersionlessIdValues(results); + + assertThat(ids, contains(id2.getValue())); + + } + + @Test + public void testSearchWithMissingQuantity() { + IIdType notMissing; + IIdType missing; + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("002"); + obs.setValue(new Quantity(123)); + notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + // Quantity Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + QuantityParam param = new QuantityParam(); + param.setMissing(false); + params.add(Observation.SP_VALUE_QUANTITY, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + QuantityParam param = new QuantityParam(); + param.setMissing(true); + params.add(Observation.SP_VALUE_QUANTITY, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithMissingReference() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId().toUnqualifiedVersionless(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // Reference Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + ReferenceParam param = new ReferenceParam(); + param.setMissing(false); + params.add(Patient.SP_ORGANIZATION, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + ReferenceParam param = new ReferenceParam(); + param.setMissing(true); + params.add(Patient.SP_ORGANIZATION, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + assertThat(patients, not(containsInRelativeOrder(orgId))); + } + } + + @Test + public void testSearchWithMissingString() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // String Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + StringParam param = new StringParam(); + param.setMissing(false); + params.add(Patient.SP_FAMILY, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + StringParam param = new StringParam(); + param.setMissing(true); + params.add(Patient.SP_FAMILY, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithNoResults() { + Device dev = new Device(); + dev.addIdentifier().setSystem("Foo"); + myDeviceDao.create(dev, mySrd); + + IBundleProvider value = myDeviceDao.search(new SearchParameterMap()); + ourLog.info("Initial size: " + value.size()); + for (IBaseResource next : value.getResources(0, value.size())) { + ourLog.info("Deleting: {}", next.getIdElement()); + myDeviceDao.delete((IIdType) next.getIdElement(), mySrd); + } + + value = myDeviceDao.search(new SearchParameterMap()); + if (value.size() > 0) { + ourLog.info("Found: " + (value.getResources(0, 1).get(0).getIdElement())); + fail(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(value.getResources(0, 1).get(0))); + } + assertEquals(0, value.size().intValue()); + + List res = value.getResources(0, 0); + assertTrue(res.isEmpty()); + + } + + @Test + public void testSearchWithRevIncludes() { + final String methodName = "testSearchWithRevIncludes"; + TransactionTemplate txTemplate = new TransactionTemplate(myTransactionMgr); + txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + IIdType pid = txTemplate.execute(new TransactionCallback() { + + @Override + public IIdType doInTransaction(TransactionStatus theStatus) { + org.hl7.fhir.r4.model.Patient p = new org.hl7.fhir.r4.model.Patient(); + p.addName().setFamily(methodName); + IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + org.hl7.fhir.r4.model.Condition c = new org.hl7.fhir.r4.model.Condition(); + c.getSubject().setReferenceElement(pid); + myConditionDao.create(c); + + return pid; + } + }); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_RES_ID, new StringParam(pid.getIdPart())); + map.addRevInclude(Condition.INCLUDE_PATIENT); + IBundleProvider results = myPatientDao.search(map); + List foundResources = results.getResources(0, results.size()); + assertEquals(Patient.class, foundResources.get(0).getClass()); + assertEquals(Condition.class, foundResources.get(1).getClass()); + } + + @Test + public void testSearchWithSecurityAndProfileParams() { + String methodName = "testSearchWithSecurityAndProfileParams"; + + IIdType tag1id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addSecurity("urn:taglist", methodName + "1a", null); + tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tag2id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addProfile("http://" + methodName); + tag2id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add("_security", new TokenParam("urn:taglist", methodName + "1a")); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add("_profile", new UriParam("http://" + methodName)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag2id)); + } + } + + @Test + public void testSearchWithTagParameter() { + String methodName = "testSearchWithTagParameter"; + + IIdType tag1id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addTag("urn:taglist", methodName + "1a", null); + org.getMeta().addTag("urn:taglist", methodName + "1b", null); + tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + + Date betweenDate = new Date(); + + IIdType tag2id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addTag("urn:taglist", methodName + "2a", null); + org.getMeta().addTag("urn:taglist", methodName + "2b", null); + tag2id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + + { + // One tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "1a")); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + { + // Code only + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam(null, methodName + "1a")); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + { + // Or tags + SearchParameterMap params = new SearchParameterMap(); + TokenOrListParam orListParam = new TokenOrListParam(); + orListParam.add(new TokenParam("urn:taglist", methodName + "1a")); + orListParam.add(new TokenParam("urn:taglist", methodName + "2a")); + params.add("_tag", orListParam); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id, tag2id)); + } + { + // Or tags with lastupdated + SearchParameterMap params = new SearchParameterMap(); + TokenOrListParam orListParam = new TokenOrListParam(); + orListParam.add(new TokenParam("urn:taglist", methodName + "1a")); + orListParam.add(new TokenParam("urn:taglist", methodName + "2a")); + params.add("_tag", orListParam); + params.setLastUpdated(new DateRangeParam(betweenDate, null)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag2id)); + } + // TODO: get multiple/AND working + { + // And tags + SearchParameterMap params = new SearchParameterMap(); + TokenAndListParam andListParam = new TokenAndListParam(); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1a")); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "2a")); + params.add("_tag", andListParam); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertEquals(0, patients.size()); + } + + { + // And tags + SearchParameterMap params = new SearchParameterMap(); + TokenAndListParam andListParam = new TokenAndListParam(); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1a")); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1b")); + params.add("_tag", andListParam); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + + } + + @Test + public void testSearchWithTagParameterMissing() { + String methodName = "testSearchWithTagParameterMissing"; + + IIdType tag1id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addTag("urn:taglist", methodName + "1a", null); + org.getMeta().addTag("urn:taglist", methodName + "1b", null); + tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + + IIdType tag2id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + org.getMeta().addTag("urn:taglist", methodName + "1b", null); + tag2id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + + { + // One tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "1a").setModifier(TokenParamModifier.NOT)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag2id)); + assertThat(patients, not(containsInAnyOrder(tag1id))); + } + { + // Non existant tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "FOO").setModifier(TokenParamModifier.NOT)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id, tag2id)); + } + { + // Common tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "1b").setModifier(TokenParamModifier.NOT)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, empty()); + } + } + + @Test + public void testSearchWithToken() { + IIdType notMissing; + IIdType missing; + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("002"); + obs.getCode().addCoding().setSystem("urn:system").setCode("002"); + notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + // Token Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + TokenParam param = new TokenParam(); + param.setMissing(false); + params.add(Observation.SP_CODE, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + TokenParam param = new TokenParam(); + param.setMissing(true); + params.add(Observation.SP_CODE, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + /** + * https://chat.fhir.org/#narrow/stream/implementers/topic/Understanding.20_include + */ + @Test + public void testSearchWithTypedInclude() { + IIdType patId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType practId; + { + Practitioner pract = new Practitioner(); + pract.addIdentifier().setSystem("urn:system").setValue("001"); + practId = myPractitionerDao.create(pract, mySrd).getId().toUnqualifiedVersionless(); + } + + Appointment appt = new Appointment(); + appt.addParticipant().getActor().setReference(patId.getValue()); + appt.addParticipant().getActor().setReference(practId.getValue()); + IIdType apptId = myAppointmentDao.create(appt, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.addInclude(Appointment.INCLUDE_PATIENT); + assertThat(toUnqualifiedVersionlessIds(myAppointmentDao.search(params)), containsInAnyOrder(patId, apptId)); + + } + + @Test + public void testSearchWithUriParam() throws Exception { + Class type = ValueSet.class; + String resourceName = "/valueset-dstu2.json"; + ValueSet vs = loadResourceFromClasspath(type, resourceName); + IIdType id1 = myValueSetDao.update(vs, mySrd).getId().toUnqualifiedVersionless(); + + ValueSet vs2 = new ValueSet(); + vs2.setUrl("http://hl7.org/foo/bar"); + myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider result; + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/basic-resource-type"))); + assertThat(toUnqualifiedVersionlessIds(result), contains(id1)); + + result = myValueSetDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/basic-resource-type").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), contains(id1)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), contains(id1)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/FOOOOOO"))); + assertThat(toUnqualifiedVersionlessIds(result), empty()); + + } + + @Test + public void testSearchWithUriParamAbove() throws Exception { + ValueSet vs1 = new ValueSet(); + vs1.setUrl("http://hl7.org/foo/baz"); + myValueSetDao.create(vs1, mySrd).getId().toUnqualifiedVersionless(); + + ValueSet vs2 = new ValueSet(); + vs2.setUrl("http://hl7.org/foo/bar"); + IIdType id2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless(); + + ValueSet vs3 = new ValueSet(); + vs3.setUrl("http://hl7.org/foo/bar/baz"); + IIdType id3 = myValueSetDao.create(vs3, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider result; + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/foo/bar/baz/boz").setQualifier(UriParamQualifierEnum.ABOVE))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2, id3)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/foo/bar/baz").setQualifier(UriParamQualifierEnum.ABOVE))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2, id3)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/foo/bar").setQualifier(UriParamQualifierEnum.ABOVE))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2)); + + result = myValueSetDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/basic-resource-type").setQualifier(UriParamQualifierEnum.ABOVE))); + assertThat(toUnqualifiedVersionlessIds(result), empty()); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org").setQualifier(UriParamQualifierEnum.ABOVE))); + assertThat(toUnqualifiedVersionlessIds(result), empty()); + } + + @Test + public void testSearchWithUriParamBelow() throws Exception { + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + Class type = ValueSet.class; + String resourceName = "/valueset-dstu2.json"; + ValueSet vs = loadResourceFromClasspath(type, resourceName); + IIdType id1 = myValueSetDao.update(vs, mySrd).getId().toUnqualifiedVersionless(); + + ValueSet vs2 = new ValueSet(); + vs2.setUrl("http://hl7.org/foo/bar"); + IIdType id2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider result; + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/foo").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2)); + + result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/foo/baz").setQualifier(UriParamQualifierEnum.BELOW))); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder()); + } + + @Test + public void testSortOnId() throws Exception { + // Numeric ID + Patient p01 = new Patient(); + p01.setActive(true); + p01.setGender(AdministrativeGender.MALE); + p01.addName().setFamily("B").addGiven("A"); + String id1 = myPatientDao.create(p01).getId().toUnqualifiedVersionless().getValue(); + + // Numeric ID + Patient p02 = new Patient(); + p02.setActive(true); + p02.setGender(AdministrativeGender.MALE); + p02.addName().setFamily("B").addGiven("B"); + p02.addName().setFamily("Z").addGiven("Z"); + String id2 = myPatientDao.create(p02).getId().toUnqualifiedVersionless().getValue(); + + // Forced ID + Patient pAB = new Patient(); + pAB.setId("AB"); + pAB.setActive(true); + pAB.setGender(AdministrativeGender.MALE); + pAB.addName().setFamily("A").addGiven("B"); + myPatientDao.update(pAB); + + // Forced ID + Patient pAA = new Patient(); + pAA.setId("AA"); + pAA.setActive(true); + pAA.setGender(AdministrativeGender.MALE); + pAA.addName().setFamily("A").addGiven("A"); + myPatientDao.update(pAA); + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.setSort(new SortSpec("_id", SortOrderEnum.ASC)); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", id1, id2)); + + } + + @Test + public void testSortOnLastUpdated() throws Exception { + // Numeric ID + Patient p01 = new Patient(); + p01.setActive(true); + p01.setGender(AdministrativeGender.MALE); + p01.addName().setFamily("B").addGiven("A"); + String id1 = myPatientDao.create(p01).getId().toUnqualifiedVersionless().getValue(); + + Thread.sleep(10); + + // Numeric ID + Patient p02 = new Patient(); + p02.setActive(true); + p02.setGender(AdministrativeGender.MALE); + p02.addName().setFamily("B").addGiven("B"); + p02.addName().setFamily("Z").addGiven("Z"); + String id2 = myPatientDao.create(p02).getId().toUnqualifiedVersionless().getValue(); + + Thread.sleep(10); + + // Forced ID + Patient pAB = new Patient(); + pAB.setId("AB"); + pAB.setActive(true); + pAB.setGender(AdministrativeGender.MALE); + pAB.addName().setFamily("A").addGiven("B"); + myPatientDao.update(pAB); + + Thread.sleep(10); + + // Forced ID + Patient pAA = new Patient(); + pAA.setId("AA"); + pAA.setActive(true); + pAA.setGender(AdministrativeGender.MALE); + pAA.addName().setFamily("A").addGiven("A"); + myPatientDao.update(pAA); + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.setSort(new SortSpec("_lastUpdated", SortOrderEnum.ASC)); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains(id1, id2, "Patient/AB", "Patient/AA")); + + } + + @Test + public void testSortOnSearchParameterWhereAllResourcesHaveAValue() throws Exception { + Patient pBA = new Patient(); + pBA.setId("BA"); + pBA.setActive(true); + pBA.setGender(AdministrativeGender.MALE); + pBA.addName().setFamily("B").addGiven("A"); + myPatientDao.update(pBA); + + Patient pBB = new Patient(); + pBB.setId("BB"); + pBB.setActive(true); + pBB.setGender(AdministrativeGender.MALE); + pBB.addName().setFamily("B").addGiven("B"); + pBB.addName().setFamily("Z").addGiven("Z"); + myPatientDao.update(pBB); + + Patient pAB = new Patient(); + pAB.setId("AB"); + pAB.setActive(true); + pAB.setGender(AdministrativeGender.MALE); + pAB.addName().setFamily("A").addGiven("B"); + myPatientDao.update(pAB); + + Patient pAA = new Patient(); + pAA.setId("AA"); + pAA.setActive(true); + pAA.setGender(AdministrativeGender.MALE); + pAA.addName().setFamily("A").addGiven("A"); + myPatientDao.update(pAA); + + SearchParameterMap map; + List ids; + + // No search param + map = new SearchParameterMap(); + map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + + // Same SP as sort + map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new TokenParam(null, "true")); + map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + + // Different SP from sort + map = new SearchParameterMap(); + map.add(Patient.SP_GENDER, new TokenParam(null, "male")); + map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + + map = new SearchParameterMap(); + map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + ourLog.info("IDS: {}", ids); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + + map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new TokenParam(null, "true")); + map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + } + + @SuppressWarnings("unused") + @Test + public void testSortOnSparselyPopulatedFields() { + IIdType pid1, pid2, pid3, pid4, pid5, pid6; + { + Patient p = new Patient(); + p.setActive(true); + pid1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient p = new Patient(); + p.addName().setFamily("A"); + pid2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient p = new Patient(); + p.addName().setFamily("B"); + pid3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient p = new Patient(); + p.addName().setFamily("B").addGiven("A"); + pid4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient p = new Patient(); + p.addName().setFamily("B").addGiven("B"); + pid5 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.setSort(new SortSpec(Patient.SP_FAMILY, SortOrderEnum.ASC).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIds(myPatientDao.search(map)); + assertThat(ids, contains(pid2, pid4, pid5, pid3, pid1)); + assertEquals(5, ids.size()); + + } + + @Test + public void testSortOnSparselyPopulatedSearchParameter() throws Exception { + Patient pCA = new Patient(); + pCA.setId("CA"); + pCA.setActive(false); + pCA.getAddressFirstRep().addLine("A"); + pCA.addName().setFamily("C").addGiven("A"); + pCA.addName().setFamily("Z").addGiven("A"); + myPatientDao.update(pCA); + + Patient pBA = new Patient(); + pBA.setId("BA"); + pBA.setActive(true); + pBA.setGender(AdministrativeGender.MALE); + pBA.addName().setFamily("B").addGiven("A"); + myPatientDao.update(pBA); + + Patient pBB = new Patient(); + pBB.setId("BB"); + pBB.setActive(true); + pBB.setGender(AdministrativeGender.MALE); + pBB.addName().setFamily("B").addGiven("B"); + myPatientDao.update(pBB); + + Patient pAB = new Patient(); + pAB.setId("AB"); + pAB.setActive(true); + pAB.setGender(AdministrativeGender.MALE); + pAB.addName().setFamily("A").addGiven("B"); + myPatientDao.update(pAB); + + Patient pAA = new Patient(); + pAA.setId("AA"); + pAA.setActive(true); + pAA.setGender(AdministrativeGender.MALE); + pAA.addName().setFamily("A").addGiven("A"); + myPatientDao.update(pAA); + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.setSort(new SortSpec("gender")); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + ourLog.info("IDS: {}", ids); + assertThat(ids, containsInAnyOrder("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB", "Patient/CA")); + + map = new SearchParameterMap(); + map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + ourLog.info("IDS: {}", ids); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB", "Patient/CA")); + + map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new TokenParam(null, "true")); + map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))); + ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); + } + + private String toStringMultiline(List theResults) { + StringBuilder b = new StringBuilder(); + for (Object next : theResults) { + b.append('\n'); + b.append(" * ").append(next.toString()); + } + return b.toString(); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java new file mode 100644 index 00000000000..9b79fe0596c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java @@ -0,0 +1,174 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; + +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.test.util.AopTestUtils; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.StringParam; + +public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test { + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @After() + public void after() { + StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); + staleSearchDeletingSvc.setCutoffSlackForUnitTest(StaleSearchDeletingSvcImpl.DEFAULT_CUTOFF_SLACK); + } + + @Before + public void before() { + StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); + staleSearchDeletingSvc.setCutoffSlackForUnitTest(0); + } + + @Test + public void testExpirePagesAfterSingleUse() throws Exception { + IIdType pid1; + IIdType pid2; + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("EXPIRE")); + final IBundleProvider bundleProvider = myPatientDao.search(params); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + + myDaoConfig.setExpireSearchResultsAfterMillis(500); + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + assertNotNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid())); + } + }); + + Thread.sleep(750); + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + assertNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid())); + } + }); + } + + @Test + public void testExpirePagesAfterReuse() throws Exception { + IIdType pid1; + IIdType pid2; + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + { + Patient patient = new Patient(); + patient.addName().setFamily("EXPIRE"); + pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + Thread.sleep(10); + + myDaoConfig.setExpireSearchResultsAfterMillis(1000L); + myDaoConfig.setReuseCachedSearchResultsForMillis(500L); + + final String searchUuid1; + { + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("EXPIRE")); + final IBundleProvider bundleProvider = myPatientDao.search(params); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + searchUuid1 = bundleProvider.getUuid(); + Validate.notBlank(searchUuid1); + } + + Thread.sleep(250); + + String searchUuid2; + { + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("EXPIRE")); + final IBundleProvider bundleProvider = myPatientDao.search(params); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + searchUuid2 = bundleProvider.getUuid(); + Validate.notBlank(searchUuid2); + } + assertEquals(searchUuid1, searchUuid2); + + Thread.sleep(500); + + // We're now past 500ms so we shouldn't reuse the search + + final String searchUuid3; + { + SearchParameterMap params = new SearchParameterMap(); + params.add(Patient.SP_FAMILY, new StringParam("EXPIRE")); + final IBundleProvider bundleProvider = myPatientDao.search(params); + assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2)); + searchUuid3 = bundleProvider.getUuid(); + Validate.notBlank(searchUuid3); + } + assertNotEquals(searchUuid1, searchUuid3); + + // Search just got used so it shouldn't be deleted + + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + newTxTemplate().execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + assertNotNull(mySearchEntityDao.findByUuid(searchUuid1)); + assertNotNull(mySearchEntityDao.findByUuid(searchUuid3)); + } + }); + + Thread.sleep(750); + + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + newTxTemplate().execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + assertNull(mySearchEntityDao.findByUuid(searchUuid1)); + assertNotNull(mySearchEntityDao.findByUuid(searchUuid3)); + } + }); + + Thread.sleep(300); + + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + newTxTemplate().execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + assertNull(mySearchEntityDao.findByUuid(searchUuid1)); + assertNull(mySearchEntityDao.findByUuid(searchUuid3)); + } + }); + + } +} 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 new file mode 100644 index 00000000000..3010af0f871 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -0,0 +1,225 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import javax.persistence.EntityManager; + +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.*; +import org.junit.*; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestR4WithoutLuceneConfig; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; +import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +// @RunWith(SpringJUnit4ClassRunner.class) +// @ContextConfiguration(classes= {TestR4WithoutLuceneConfig.class}) +// @SuppressWarnings("unchecked") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { TestR4WithoutLuceneConfig.class }) +public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchWithLuceneDisabledTest.class); + + @Autowired + @Qualifier("myAllergyIntoleranceDaoR4") + private IFhirResourceDao myAllergyIntoleranceDao; + @Autowired + @Qualifier("myAppointmentDaoR4") + private IFhirResourceDao myAppointmentDao; + @Autowired + @Qualifier("myAuditEventDaoR4") + private IFhirResourceDao myAuditEventDao; + @Autowired + @Qualifier("myBundleDaoR4") + private IFhirResourceDao myBundleDao; + @Autowired + @Qualifier("myCarePlanDaoR4") + private IFhirResourceDao myCarePlanDao; + @Autowired + @Qualifier("myCodeSystemDaoR4") + private IFhirResourceDao myCodeSystemDao; + @Autowired + @Qualifier("myCompartmentDefinitionDaoR4") + private IFhirResourceDao myCompartmentDefinitionDao; + @Autowired + @Qualifier("myConceptMapDaoR4") + private IFhirResourceDao myConceptMapDao; + @Autowired + @Qualifier("myConditionDaoR4") + private IFhirResourceDao myConditionDao; + @Autowired + protected DaoConfig myDaoConfig; + @Autowired + @Qualifier("myDeviceDaoR4") + private IFhirResourceDao myDeviceDao; + @Autowired + @Qualifier("myDiagnosticReportDaoR4") + private IFhirResourceDao myDiagnosticReportDao; + @Autowired + @Qualifier("myEncounterDaoR4") + private IFhirResourceDao myEncounterDao; + // @PersistenceContext() + @Autowired + private EntityManager myEntityManager; + @Autowired + private FhirContext myFhirCtx; + @Autowired + @Qualifier("myImmunizationDaoR4") + private IFhirResourceDao myImmunizationDao; + @Autowired + @Qualifier("myLocationDaoR4") + private IFhirResourceDao myLocationDao; + @Autowired + @Qualifier("myMediaDaoR4") + private IFhirResourceDao myMediaDao; + @Autowired + @Qualifier("myMedicationDaoR4") + private IFhirResourceDao myMedicationDao; + @Autowired + @Qualifier("myMedicationRequestDaoR4") + private IFhirResourceDao myMedicationRequestDao; + @Autowired + @Qualifier("myNamingSystemDaoR4") + private IFhirResourceDao myNamingSystemDao; + @Autowired + @Qualifier("myObservationDaoR4") + private IFhirResourceDao myObservationDao; + @Autowired + @Qualifier("myOperationDefinitionDaoR4") + private IFhirResourceDao myOperationDefinitionDao; + @Autowired + @Qualifier("myOrganizationDaoR4") + private IFhirResourceDao myOrganizationDao; + @Autowired + @Qualifier("myPatientDaoR4") + private IFhirResourceDaoPatient myPatientDao; + @Autowired + @Qualifier("myPractitionerDaoR4") + private IFhirResourceDao myPractitionerDao; + @Autowired + @Qualifier("myQuestionnaireDaoR4") + private IFhirResourceDao myQuestionnaireDao; + @Autowired + @Qualifier("myQuestionnaireResponseDaoR4") + private IFhirResourceDao myQuestionnaireResponseDao; + @Autowired + @Qualifier("myResourceProvidersR4") + private Object myResourceProviders; + @Autowired + @Qualifier("myStructureDefinitionDaoR4") + private IFhirResourceDao myStructureDefinitionDao; + @Autowired + @Qualifier("mySubscriptionDaoR4") + private IFhirResourceDaoSubscription mySubscriptionDao; + @Autowired + @Qualifier("mySubstanceDaoR4") + private IFhirResourceDao mySubstanceDao; + @Autowired + @Qualifier("mySystemDaoR4") + private IFhirSystemDao mySystemDao; + @Autowired + @Qualifier("mySystemProviderR4") + private JpaSystemProviderR4 mySystemProvider; + + @Autowired + protected PlatformTransactionManager myTxManager; + @Autowired + protected ISearchParamPresenceSvc mySearchParamPresenceSvc; + + @Autowired + @Qualifier("myJpaValidationSupportChainR4") + private IValidationSupport myValidationSupport; + @Autowired + protected ISearchCoordinatorSvc mySearchCoordinatorSvc; + + @Before + @Transactional() + public void beforePurgeDatabase() { + final EntityManager entityManager = this.myEntityManager; + purgeDatabase(entityManager, myTxManager, mySearchParamPresenceSvc, mySearchCoordinatorSvc); + } + + @Before + public void beforeResetConfig() { + myDaoConfig.setHardSearchLimit(1000); + myDaoConfig.setHardTagListLimit(1000); + myDaoConfig.setIncludeLimit(2000); + } + + @Override + protected FhirContext getContext() { + return myFhirCtx; + } + + @Test + public void testSearchWithRegularParam() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Organization.SP_NAME, new StringParam(methodName)); + myOrganizationDao.search(map); + + } + + @Test + public void testSearchWithContent() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, new StringParam(methodName)); + try { + myOrganizationDao.search(map).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _content", e.getMessage()); + } + } + + @Test + public void testSearchWithText() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, new StringParam(methodName)); + try { + myOrganizationDao.search(map).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _text", e.getMessage()); + } + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java new file mode 100644 index 00000000000..278144a1d50 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java @@ -0,0 +1,530 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Date; +import java.util.List; + +import javax.persistence.TypedQuery; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; +import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4SubscriptionTest extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SubscriptionTest.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Autowired + private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao; + + @Autowired + private ISubscriptionTableDao mySubscriptionTableDao; + + @Before + public void beforeEnableSubscription() { + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(60); + } + + @Test + public void testSubscriptionGetsPurgedIfItIsNeverActive() throws Exception { + myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1); + + Subscription subs = new Subscription(); + subs.setCriteria("Observation?subject=Patient/123"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setStatus(SubscriptionStatus.REQUESTED); + + IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); + mySubscriptionDao.purgeInactiveSubscriptions(); + mySubscriptionDao.read(id, mySrd); + + Thread.sleep(1500); + + myDaoConfig.setSchedulingDisabled(false); + mySubscriptionDao.purgeInactiveSubscriptions(); + try { + mySubscriptionDao.read(id, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + } + + @Before + public void beforeDisableScheduling() { + myDaoConfig.setSchedulingDisabled(true); + } + + + @Test + public void testSubscriptionGetsPurgedIfItIsInactive() throws Exception { + myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1); + + Subscription subs = new Subscription(); + subs.setCriteria("Observation?subject=Patient/123"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setStatus(SubscriptionStatus.REQUESTED); + + IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); + mySubscriptionDao.purgeInactiveSubscriptions(); + mySubscriptionDao.read(id, mySrd); + + mySubscriptionDao.getUndeliveredResourcesAndPurge(mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id)); + + Thread.sleep(1500); + + myDaoConfig.setSchedulingDisabled(false); + mySubscriptionDao.purgeInactiveSubscriptions(); + try { + mySubscriptionDao.read(id, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + } + + @Test + public void testCreateSubscription() { + Subscription subs = new Subscription(); + subs.setCriteria("Observation?subject=Patient/123"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setStatus(SubscriptionStatus.REQUESTED); + + IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); + + TypedQuery q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class); + q.setParameter("id", id.getIdPartAsLong()); + final SubscriptionTable table = q.getSingleResult(); + + assertNotNull(table); + assertNotNull(table.getNextCheck()); + assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue()); + assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus()); + assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); + + subs.setStatus(SubscriptionStatus.ACTIVE); + mySubscriptionDao.update(subs, mySrd); + + assertEquals(SubscriptionStatus.ACTIVE.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus()); + assertEquals(SubscriptionStatus.ACTIVE, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); + + mySubscriptionDao.delete(id, mySrd); + + assertNull(myEntityManager.find(SubscriptionTable.class, table.getId())); + + /* + * Re-create again + */ + + subs = new Subscription(); + subs.setCriteria("Observation?subject=Patient/123"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setId(id); + subs.setStatus(SubscriptionStatus.REQUESTED); + mySubscriptionDao.update(subs, mySrd); + + assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus()); + assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); + } + + @Test + public void testCreateSubscriptionInvalidCriteria() { + Subscription subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\"")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("http://foo.com/Observation?AAA=BBB"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\"")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("ObservationZZZZ?a=b"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.channel.payload must be populated for rest-hook subscriptions")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("text/html"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Invalid value for Subscription.channel.payload: text/html")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("application/fhir+xml"); + try { + mySubscriptionDao.create(subs, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Rest-hook subscriptions must have Subscription.channel.endpoint defined")); + } + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart()); + + subs = new Subscription(); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("application/fhir+json"); + subs.getChannel().setEndpoint("http://localhost:8080"); + assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart()); + + } + + @Test + public void testDeleteSubscriptionWithFlaggedResources() throws Exception { + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testDeleteSubscriptionWithFlaggedResources"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Subscription subs; + + /* + * Create 2 identical subscriptions + */ + + subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatus.ACTIVE); + IIdType subsId = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); + Long subsPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(subsId); + + assertNull(mySubscriptionTableDao.findOne(subsPid).getLastClientPoll()); + + Thread.sleep(100); + ourLog.info("Before: {}", System.currentTimeMillis()); + assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); + assertThat(mySubscriptionTableDao.count(), equalTo(1L)); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + ourLog.info("After: {}", System.currentTimeMillis()); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L)); + assertThat(mySubscriptionTableDao.count(), greaterThan(0L)); + + /* + * Delete the subscription + */ + + mySubscriptionDao.delete(subsId, mySrd); + + assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); + assertThat(mySubscriptionTableDao.count(), not(greaterThan(0L))); + + /* + * Delete a second time just to make sure that works + */ + mySubscriptionDao.delete(subsId, mySrd); + + /* + * Re-create the subscription + */ + + subs.setId(subsId); + mySubscriptionDao.update(subs, mySrd).getId(); + + assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); + assertThat(mySubscriptionTableDao.count(), (greaterThan(0L))); + + /* + * Create another resource and make sure it gets flagged + */ + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L)); + assertThat(mySubscriptionTableDao.count(), greaterThan(0L)); + + } + + @Test + public void testSubscriptionResourcesAppear() throws Exception { + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testSubscriptionResourcesAppear"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Subscription subs; + + /* + * Create 2 identical subscriptions + */ + + subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatus.ACTIVE); + Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); + + subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatus.ACTIVE); + Long subsId2 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); + + assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); + + Thread.sleep(100); + ourLog.info("Before: {}", System.currentTimeMillis()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + ourLog.info("After: {}", System.currentTimeMillis()); + + List results; + List resultIds; + + assertEquals(4, mySubscriptionDao.pollForNewUndeliveredResources()); + assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); + + results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1); + resultIds = toUnqualifiedVersionlessIds(results); + assertThat(resultIds, contains(afterId1, afterId2)); + + Date lastClientPoll = mySubscriptionTableDao.findOne(subsId1).getLastClientPoll(); + assertNotNull(lastClientPoll); + + mySubscriptionDao.pollForNewUndeliveredResources(); + results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2); + resultIds = toUnqualifiedVersionlessIds(results); + assertThat(resultIds, contains(afterId1, afterId2)); + + mySubscriptionDao.pollForNewUndeliveredResources(); + results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1); + resultIds = toUnqualifiedVersionlessIds(results); + assertThat(resultIds, empty()); + + assertNotEquals(lastClientPoll, mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); + + mySubscriptionDao.pollForNewUndeliveredResources(); + results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2); + resultIds = toUnqualifiedVersionlessIds(results); + assertThat(resultIds, empty()); + + /* + * Make sure that reindexing doesn't trigger + */ + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(100); + + assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); + + /* + * Update resources on disk + */ + IBundleProvider allObs = myObservationDao.search(new SearchParameterMap()); + ourLog.info("Updating {} observations", allObs.size()); + for (IBaseResource next : allObs.getResources(0, allObs.size())) { + ourLog.info("Updating observation"); + Observation nextObs = (Observation) next; + nextObs.addPerformer().setDisplay("Some display"); + myObservationDao.update(nextObs, mySrd); + } + + assertEquals(6, mySubscriptionDao.pollForNewUndeliveredResources()); + assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); + + } + + + @Test + public void testSubscriptionResourcesAppear2() throws Exception { + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testSubscriptionResourcesAppear2"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType oId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Subscription subs; + + /* + * Create 2 identical subscriptions + */ + + subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatus.ACTIVE); + Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); + + assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); + + assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); + + ourLog.info("pId: {} - oId: {}", pId, oId); + + myObservationDao.update(myObservationDao.read(oId, mySrd), mySrd); + + assertEquals(1, mySubscriptionDao.pollForNewUndeliveredResources()); + ourLog.info("Between passes"); + assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); + + Thread.sleep(100); + ourLog.info("Before: {}", System.currentTimeMillis()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + ourLog.info("After: {}", System.currentTimeMillis()); + + assertEquals(2, mySubscriptionDao.pollForNewUndeliveredResources()); + assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); + + Thread.sleep(100); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); + + Thread.sleep(100); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); + + Thread.sleep(100); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertEquals(4, mySubscriptionFlaggedResourceDataDao.count()); + + Thread.sleep(100); + + mySubscriptionDao.pollForNewUndeliveredResources(); + assertEquals(4, mySubscriptionFlaggedResourceDataDao.count()); + } + + +} 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 new file mode 100644 index 00000000000..09983667d92 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -0,0 +1,1208 @@ +package ca.uhn.fhir.jpa.dao.r4; + +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.fail; + +import java.util.*; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory; +import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; +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.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; + +public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TerminologyTest.class); + public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; + public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; + + @Autowired + private IHapiTerminologySvc myHapiTerminologySvc; + + @After + public void after() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); + + BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(false); + } + + @Before + public void before() { + myDaoConfig.setMaximumExpansionSize(5000); +// my + } + + private CodeSystem createExternalCs() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA"); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA"); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB"); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB"); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); + cs.getConcepts().add(parentB); + + TermConcept childBA = new TermConcept(cs, "childBA").setDisplay("Child BA"); + childBA.addChild(childAAB, RelationshipTypeEnum.ISA); + parentB.addChild(childBA, RelationshipTypeEnum.ISA); + + TermConcept parentC = new TermConcept(cs, "ParentC").setDisplay("Parent C"); + cs.getConcepts().add(parentC); + + TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); + parentC.addChild(childCA, RelationshipTypeEnum.ISA); + + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + return codeSystem; + } + + private CodeSystem createExternalCsLarge() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "codeA").setDisplay("CodeA"); + cs.getConcepts().add(parentA); + + for (int i = 0; i < 450; i++) { + TermConcept childI = new TermConcept(cs, "subCodeA"+i).setDisplay("Sub-code A"+i); + parentA.addChild(childI, RelationshipTypeEnum.ISA); + } + + TermConcept parentB = new TermConcept(cs, "codeB").setDisplay("CodeB"); + cs.getConcepts().add(parentB); + + for (int i = 0; i < 450; i++) { + TermConcept childI = new TermConcept(cs, "subCodeB"+i).setDisplay("Sub-code B"+i); + parentB.addChild(childI, RelationshipTypeEnum.ISA); + } + + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + return codeSystem; + } + + private void createExternalCsAndLocalVs() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVs(codeSystem); + } + + private CodeSystem createExternalCsDogs() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept hello = new TermConcept(cs, "hello").setDisplay("Hello"); + cs.getConcepts().add(hello); + + TermConcept goodbye = new TermConcept(cs, "goodbye").setDisplay("Goodbye"); + cs.getConcepts().add(goodbye); + + TermConcept dogs = new TermConcept(cs, "dogs").setDisplay("Dogs"); + cs.getConcepts().add(dogs); + + TermConcept labrador = new TermConcept(cs, "labrador").setDisplay("Labrador"); + dogs.addChild(labrador, RelationshipTypeEnum.ISA); + + TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle"); + dogs.addChild(beagle, RelationshipTypeEnum.ISA); + + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + return codeSystem; + } + + private void createLocalCsAndVs() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code B") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + //@formatter:on + myCodeSystemDao.create(codeSystem, mySrd); + + createLocalVs(codeSystem); + } + + private void createLocalVs(CodeSystem codeSystem) { + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose().addInclude().setSystem(codeSystem.getUrl()); + myValueSetDao.create(valueSet, mySrd); + } + + private void logAndValidateValueSet(ValueSet theResult) { + IParser parser = myFhirCtx.newXmlParser().setPrettyPrint(true); + String encoded = parser.encodeResourceToString(theResult); + ourLog.info(encoded); + + FhirValidator validator = myFhirCtx.newValidator(); + validator.setValidateAgainstStandardSchema(true); + validator.setValidateAgainstStandardSchematron(true); + ValidationResult result = validator.validateWithResult(theResult); + + if (!result.isSuccessful()) { + ourLog.info(parser.encodeResourceToString(result.toOperationOutcome())); + fail(parser.encodeResourceToString(result.toOperationOutcome())); + } + } + + @Test + public void testCodeSystemCreateDuplicateFails() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + try { + myCodeSystemDao.create(codeSystem, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); + } + } + + @Test + public void testCodeSystemWithDefinedCodes() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code AB")); + //@formatter:on + + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + Set codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A"); + assertThat(toCodes(codes), containsInAnyOrder("A", "AA", "AB")); + + } + + @Test + public void testExpandInvalid() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter(); + include.addFilter().setOp(FilterOperator.ISA).setValue("childAA"); + + try { + myValueSetDao.expand(vs, null); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid filter, must have fields populated: property op value", e.getMessage()); + } + } + + @Test + public void testExpandWithCodesAndDisplayFilterBlank() { + CodeSystem codeSystem = createExternalCsDogs(); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addConcept(new ConceptReferenceComponent().setCode("hello")) + .addConcept(new ConceptReferenceComponent().setCode("goodbye")); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addFilter() + .setProperty("concept") + .setOp(FilterOperator.ISA) + .setValue("dogs"); + + myValueSetDao.create(valueSet, mySrd); + + ValueSet result = myValueSetDao.expand(valueSet, ""); + logAndValidateValueSet(result); + + assertEquals(4, result.getExpansion().getTotal()); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("hello", "goodbye", "labrador", "beagle")); + + } + + // TODO: get this working + @Ignore + @Test + public void testExpandWithOpEquals() { + + + ValueSet result = myValueSetDao.expandByIdentifier("http://hl7.org/fhir/ValueSet/doc-typecodes", ""); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result)); + } + + + @Test + public void testExpandWithCodesAndDisplayFilterPartialOnFilter() { + CodeSystem codeSystem = createExternalCsDogs(); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addConcept(new ConceptReferenceComponent().setCode("hello")) + .addConcept(new ConceptReferenceComponent().setCode("goodbye")); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addFilter() + .setProperty("concept") + .setOp(FilterOperator.ISA) + .setValue("dogs"); + + myValueSetDao.create(valueSet, mySrd); + + ValueSet result = myValueSetDao.expand(valueSet, "lab"); + logAndValidateValueSet(result); + + assertEquals(1, result.getExpansion().getTotal()); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("labrador")); + + } + + @Test + public void testExpandWithCodesAndDisplayFilterPartialOnCodes() { + CodeSystem codeSystem = createExternalCsDogs(); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addConcept(new ConceptReferenceComponent().setCode("hello")) + .addConcept(new ConceptReferenceComponent().setCode("goodbye")); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()) + .addFilter() + .setProperty("concept") + .setOp(FilterOperator.ISA) + .setValue("dogs"); + + myValueSetDao.create(valueSet, mySrd); + + ValueSet result = myValueSetDao.expand(valueSet, "hel"); + logAndValidateValueSet(result); + + assertEquals(1, result.getExpansion().getTotal()); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("hello")); + + } + + @Test + public void testExpandWithCodesAndDisplayFilterPartialOnExpansion() { + CodeSystem codeSystem = createExternalCsDogs(); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose().addInclude().setSystem(codeSystem.getUrl()); + myValueSetDao.create(valueSet, mySrd); + + ValueSet result = myValueSetDao.expand(valueSet, "lab"); + logAndValidateValueSet(result); + + assertEquals(1, result.getExpansion().getTotal()); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("labrador")); + + } + + @Test + public void testExpandWithDisplayInExternalValueSetFuzzyMatching() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("parent a"); + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA")); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("pare"); + result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "ParentC")); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display:exact").setOp(FilterOperator.EQUAL).setValue("pare"); + result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, empty()); + + } + + @Test + public void testExpandWithExcludeInExternalValueSet() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + + ConceptSetComponent exclude = vs.getCompose().addExclude(); + exclude.setSystem(URL_MY_CODE_SYSTEM); + exclude.addConcept().setCode("childAA"); + exclude.addConcept().setCode("childAAA"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "childAB", "childAAB", "ParentC", "childBA", "childCA")); + } + + @Test + public void testExpandWithInvalidExclude() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + + /* + * No system set on exclude + */ + ConceptSetComponent exclude = vs.getCompose().addExclude(); + exclude.addConcept().setCode("childAA"); + exclude.addConcept().setCode("childAAA"); + try { + myValueSetDao.expand(vs, null); + fail(); + } catch (InvalidRequestException e) { + assertEquals("ValueSet contains exclude criteria with no system defined", e.getMessage()); + } + } + + @Test + public void testExpandWithIsAInExternalValueSet() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setOp(FilterOperator.ISA).setValue("childAA").setProperty("concept"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + + } + + @Test + public void testExpandWithIsAInExternalValueSetReindex() { + BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(true); + + createExternalCsAndLocalVs(); + + mySystemDao.markAllResourcesForReindexing(); + + mySystemDao.performReindexingPass(100); + mySystemDao.performReindexingPass(100); + myHapiTerminologySvc.saveDeferred(); + myHapiTerminologySvc.saveDeferred(); + myHapiTerminologySvc.saveDeferred(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setOp(FilterOperator.ISA).setValue("childAA").setProperty("concept"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + + } + + @Test + public void testExpandWithNoResultsInLocalValueSet1() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("ZZZZ"); + + try { + myValueSetDao.expand(vs, null); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to find code 'ZZZZ' in code system http://example.com/my_code_system", e.getMessage()); + } + + } + + @Test + public void testExpandWithNoResultsInLocalValueSet2() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM + "AA"); + include.addConcept().setCode("A"); + + try { + myValueSetDao.expand(vs, null); + fail(); + } catch (InvalidRequestException e) { + assertEquals("unable to find code system http://example.com/my_code_systemAA", e.getMessage()); + } + } + + @Test + public void testExpandWithSystemAndCodesAndFilterKeywordInLocalValueSet() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("A"); + + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("AAA"); + + ValueSet result = myValueSetDao.expand(vs, null); + + // Technically it's not valid to expand a ValueSet with both includes and filters so the + // result fails validation because of the input.. we're being permissive by allowing both + // though, so we won't validate the input + result.setCompose(new ValueSetComposeComponent()); + + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("A", "AAA")); + + int idx = codes.indexOf("AAA"); + assertEquals("AAA", result.getExpansion().getContains().get(idx).getCode()); + assertEquals("Code AAA", result.getExpansion().getContains().get(idx).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); + // + } + + @Test + public void testExpandWithSystemAndCodesInExternalValueSet() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("ParentA"); + include.addConcept().setCode("childAA"); + include.addConcept().setCode("childAAA"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA")); + + int idx = codes.indexOf("childAA"); + assertEquals("childAA", result.getExpansion().getContains().get(idx).getCode()); + assertEquals("Child AA", result.getExpansion().getContains().get(idx).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); + } + + @Test + public void testExpandWithSystemAndCodesInLocalValueSet() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("A"); + include.addConcept().setCode("AA"); + include.addConcept().setCode("AAA"); + include.addConcept().setCode("AB"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("A", "AA", "AAA", "AB")); + + int idx = codes.indexOf("AAA"); + assertEquals("AAA", result.getExpansion().getContains().get(idx).getCode()); + assertEquals("Code AAA", result.getExpansion().getContains().get(idx).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); + // ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); + // ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); + // + } + + @Test + public void testExpandWithSystemAndDisplayFilterBlank() { + CodeSystem codeSystem = createExternalCsDogs(); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose() + .addInclude() + .setSystem(codeSystem.getUrl()); + + ValueSet result = myValueSetDao.expand(valueSet, ""); + logAndValidateValueSet(result); + + assertEquals(5, result.getExpansion().getTotal()); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("hello", "goodbye", "dogs", "labrador", "beagle")); + + } + + @Test + public void testExpandWithSystemAndFilterInExternalValueSet() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("Parent B"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentB")); + + } + + @Test + public void testIndexingIsDeferredForLargeCodeSystems() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); + + myTermSvc.setProcessDeferred(false); + + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + + ValueSet result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + assertEquals(0, result.getExpansion().getContains().size()); + + myTermSvc.setProcessDeferred(true); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + result = myValueSetDao.expand(vs, null); + logAndValidateValueSet(result); + + assertEquals(4, result.getExpansion().getContains().size()); + + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + assertThat(encoded, containsStringIgnoringCase("")); + } + + @Test + public void testLookupSnomed() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl("http://snomed.info/sct"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); + cs.getConcepts().add(parentA); + myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", cs); + + StringType code = new StringType("ParentA"); + StringType system = new StringType("http://snomed.info/sct"); + LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); + assertEquals(true, outcome.isFound()); + } + + /** + * Can't currently abort costly + */ + @Test + @Ignore + public void testRefuseCostlyExpansionFhirCodesystem() { + createLocalCsAndVs(); + myDaoConfig.setMaximumExpansionSize(1); + + SearchParameterMap params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); + try { + myAuditEventDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("", e.getMessage()); + } + } + + @Test + public void testRefuseCostlyExpansionLocalCodesystem() { + createLocalCsAndVs(); + myDaoConfig.setMaximumExpansionSize(1); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); + try { + myObservationDao.search(params).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage()); + } + } + + @Test + public void testReindex() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("ZZZZ"); + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(null); + myTermSvc.saveDeferred(); + mySystemDao.performReindexingPass(null); + myTermSvc.saveDeferred(); + + // Again + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(null); + myTermSvc.saveDeferred(); + mySystemDao.performReindexingPass(null); + myTermSvc.saveDeferred(); + + } + + @Test + public void testSearchCodeAboveLocalCodesystem() { + createLocalCsAndVs(); + + Observation obsAA = new Observation(); + obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); + IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsBA = new Observation(); + obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); + IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + + @Test + public void testSearchCodeBelowAndAboveUnknownCodeSystem() { + + SearchParameterMap params = new SearchParameterMap(); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + + @Test + public void testSearchCodeInUnknownCodeSystem() { + + SearchParameterMap params = new SearchParameterMap(); + + try { + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + } catch (InvalidRequestException e) { + assertEquals("Unable to find imported value set http://example.com/my_value_set", e.getMessage()); + } + } + + @Test + public void testSearchCodeBelowBuiltInCodesystem() { + AllergyIntolerance ai1 = new AllergyIntolerance(); + ai1.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE); + String id1 = myAllergyIntoleranceDao.create(ai1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai2 = new AllergyIntolerance(); + ai2.setClinicalStatus(AllergyIntoleranceClinicalStatus.RESOLVED); + String id2 = myAllergyIntoleranceDao.create(ai2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai3 = new AllergyIntolerance(); + ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); + String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status", AllergyIntoleranceClinicalStatus.ACTIVE.toCode())); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status", AllergyIntoleranceClinicalStatus.ACTIVE.toCode()).setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status", AllergyIntoleranceClinicalStatus.RESOLVED.toCode()).setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id2)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status", AllergyIntoleranceClinicalStatus.RESOLVED.toCode())); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id2)); + + // Unknown code + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status", "fooooo")); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), empty()); + + // Unknown system + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam("http://hl7.org/fhir/allergy-clinical-status222222", "fooooo")); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), empty()); + + } + + @Test + public void testSearchCodeBelowBuiltInCodesystemUnqualified() { + AllergyIntolerance ai1 = new AllergyIntolerance(); + ai1.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE); + ai1.addCategory(org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory.MEDICATION); + String id1 = myAllergyIntoleranceDao.create(ai1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai2 = new AllergyIntolerance(); + ai2.setClinicalStatus(AllergyIntoleranceClinicalStatus.RESOLVED); + ai1.addCategory(org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory.BIOLOGIC); + String id2 = myAllergyIntoleranceDao.create(ai2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai3 = new AllergyIntolerance(); + ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); + ai1.addCategory(org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory.FOOD); + String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, AllergyIntoleranceClinicalStatus.ACTIVE.toCode())); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, AllergyIntoleranceClinicalStatus.ACTIVE.toCode()).setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CATEGORY, new TokenParam(null, AllergyIntoleranceCategory.MEDICATION.toCode()).setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, AllergyIntoleranceClinicalStatus.RESOLVED.toCode()).setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id2)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, AllergyIntoleranceClinicalStatus.RESOLVED.toCode())); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id2)); + + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "FOO")); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), empty()); + + } + + + @Test + public void testSearchCodeBelowLocalCodesystem() { + createLocalCsAndVs(); + + Observation obsAA = new Observation(); + obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); + IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsBA = new Observation(); + obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); + IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + + @Test + public void testSearchCodeBelowExternalCodesystemLarge() { + createExternalCsLarge(); + + Observation obs0 = new Observation(); + obs0.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("codeA"); + IIdType id0 = myObservationDao.create(obs0, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("subCodeA1"); + IIdType id1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("subCodeA2"); + IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs3 = new Observation(); + obs3.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("subCodeB3"); + IIdType id3 = myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "codeA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(id0.getValue(), id1.getValue(), id2.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "subCodeB1").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + + @Test + public void testSearchCodeInBuiltInValueSet() { + AllergyIntolerance ai1 = new AllergyIntolerance(); + ai1.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE); + String id1 = myAllergyIntoleranceDao.create(ai1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai2 = new AllergyIntolerance(); + ai2.setClinicalStatus(AllergyIntoleranceClinicalStatus.RESOLVED); + String id2 = myAllergyIntoleranceDao.create(ai2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai3 = new AllergyIntolerance(); + ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); + String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/allergy-clinical-status").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1, id2, id3)); + + // No codes in this one + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/allergy-intolerance-criticality").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), empty()); + + // Invalid VS + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/FOO").setModifier(TokenParamModifier.IN)); + try { + myAllergyIntoleranceDao.search(params); + } catch (InvalidRequestException e) { + assertEquals("Unable to find imported value set http://hl7.org/fhir/ValueSet/FOO", e.getMessage()); + } + + } + + @Test + public void testSearchCodeInEmptyValueSet() { + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + myValueSetDao.create(valueSet, mySrd); + + SearchParameterMap params; + + ourLog.info("testSearchCodeInEmptyValueSet without status"); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + ourLog.info("testSearchCodeInEmptyValueSet with status"); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + params.add(Observation.SP_STATUS, new TokenParam(null, "final")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + ourLog.info("testSearchCodeInEmptyValueSet done"); + } + + @Test + public void testSearchCodeInExternalCodesystem() { + createExternalCsAndLocalVs(); + + Observation obsPA = new Observation(); + obsPA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("ParentA"); + IIdType idPA = myObservationDao.create(obsPA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAA = new Observation(); + obsAAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAA"); + IIdType idAAA = myObservationDao.create(obsAAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAB = new Observation(); + obsAAB.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAB"); + IIdType idAAB = myObservationDao.create(obsAAB, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAAA.getValue(), idAAB.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue(), idAAA.getValue(), idAAB.getValue())); + + } + + @Test + public void testSearchCodeInFhirCodesystem() { + createLocalCsAndVs(); + + AuditEvent aeIn1 = new AuditEvent(); + aeIn1.getType().setSystem("http://dicom.nema.org/resources/ontology/DCM").setCode("110102"); + IIdType idIn1 = myAuditEventDao.create(aeIn1, mySrd).getId().toUnqualifiedVersionless(); + + AuditEvent aeIn2 = new AuditEvent(); + aeIn2.getType().setSystem("http://hl7.org/fhir/audit-event-type").setCode("rest"); + IIdType idIn2 = myAuditEventDao.create(aeIn2, mySrd).getId().toUnqualifiedVersionless(); + + AuditEvent aeOut1 = new AuditEvent(); + aeOut1.getType().setSystem("http://example.com").setCode("foo"); + IIdType idOut1 = myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), containsInAnyOrder(idIn1.getValue(), idIn2.getValue())); + + params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/v3-PurposeOfUse").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), empty()); + } + + + @Test + public void testSearchCodeInLocalCodesystem() { + createLocalCsAndVs(); + + Observation obsAA = new Observation(); + obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); + IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsBA = new Observation(); + obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); + IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue(), idBA.getValue())); + + } + + @Test + public void testSearchCodeInValueSetThatImportsInvalidCodeSystem() { + ValueSet valueSet = new ValueSet(); + valueSet.getCompose().addInclude().addValueSet("http://non_existant_VS"); + valueSet.setUrl(URL_MY_VALUE_SET); + IIdType vsid = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params; + + ourLog.info("testSearchCodeInEmptyValueSet without status"); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + try { + myObservationDao.search(params); + } catch(InvalidRequestException e) { + assertEquals("Unable to expand imported value set: Unable to find imported value set http://non_existant_VS", e.getMessage()); + } + + // Now let's update + valueSet = new ValueSet(); + valueSet.setId(vsid); + valueSet.getCompose().addInclude().setSystem("http://hl7.org/fhir/v3/MaritalStatus").addConcept().setCode("A"); + valueSet.setUrl(URL_MY_VALUE_SET); + myValueSetDao.update(valueSet, mySrd).getId().toUnqualifiedVersionless(); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + params.add(Observation.SP_STATUS, new TokenParam(null, "final")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + + /** + * Todo: not yet implemented + */ + @Test + @Ignore + public void testSearchCodeNotInBuiltInValueSet() { + AllergyIntolerance ai1 = new AllergyIntolerance(); + ai1.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE); + String id1 = myAllergyIntoleranceDao.create(ai1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai2 = new AllergyIntolerance(); + ai2.setClinicalStatus(AllergyIntoleranceClinicalStatus.RESOLVED); + String id2 = myAllergyIntoleranceDao.create(ai2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + AllergyIntolerance ai3 = new AllergyIntolerance(); + ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); + String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap params; + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/allergy-intolerance-status").setModifier(TokenParamModifier.NOT_IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), empty()); + + // No codes in this one + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/allergy-intolerance-criticality").setModifier(TokenParamModifier.NOT_IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAllergyIntoleranceDao.search(params)), containsInAnyOrder(id1, id2, id3)); + + // Invalid VS + params = new SearchParameterMap(); + params.add(AllergyIntolerance.SP_CLINICAL_STATUS, new TokenParam(null, "http://hl7.org/fhir/ValueSet/FOO").setModifier(TokenParamModifier.NOT_IN)); + try { + myAllergyIntoleranceDao.search(params); + } catch (InvalidRequestException e) { + assertEquals("Unable to find imported value set http://hl7.org/fhir/ValueSet/FOO", e.getMessage()); + } + + } + + private ArrayList toCodesContains(List theContains) { + ArrayList retVal = new ArrayList(); + for (ValueSetExpansionContainsComponent next : theContains) { + retVal.add(next.getCode()); + } + return retVal; + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java new file mode 100644 index 00000000000..a7617ece8ae --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -0,0 +1,3588 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +import java.util.*; + +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.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.r4.model.OperationOutcome.IssueType; +import org.hl7.fhir.r4.model.Quantity.QuantityComparator; +import org.hl7.fhir.instance.model.api.*; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import com.google.common.base.Charsets; +import com.google.common.collect.Lists; + +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +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.server.IBundleProvider; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +@SuppressWarnings({ "unchecked", "deprecation" }) +public class FhirResourceDaoR4Test extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4Test.class); + + @After + public final void after() { + myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); + } + + private void assertGone(IIdType theId) { + try { + assertNotGone(theId); + fail(); + } catch (ResourceGoneException e) { + // good + } + } + + /** + * This gets called from assertGone too! Careful about exceptions... + */ + private void assertNotGone(IIdType theId) { + if ("Patient".equals(theId.getResourceType())) { + myPatientDao.read(theId, mySrd); + } else if ("Organization".equals(theId.getResourceType())) { + myOrganizationDao.read(theId, mySrd); + } else if ("CodeSystem".equals(theId.getResourceType())) { + myCodeSystemDao.read(theId, mySrd); + } else { + fail("Can't handle type: " + theId.getResourceType()); + } + } + + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + private List extractNames(IBundleProvider theSearch) { + ArrayList retVal = new ArrayList(); + for (IBaseResource next : theSearch.getResources(0, theSearch.size())) { + Patient nextPt = (Patient) next; + retVal.add(nextPt.getName().get(0).getNameAsSingleString()); + } + return retVal; + } + + private CodeableConcept newCodeableConcept(String theSystem, String theCode) { + CodeableConcept retVal = new CodeableConcept(); + retVal.addCoding().setSystem(theSystem).setCode(theCode); + return retVal; + } + + private void sort(ArrayList thePublished) { + ArrayList tags = new ArrayList(thePublished); + Collections.sort(tags, new Comparator() { + @Override + public int compare(Coding theO1, Coding theO2) { + int retVal = defaultString(theO1.getSystem()).compareTo(defaultString(theO2.getSystem())); + if (retVal == 0) { + retVal = defaultString(theO1.getCode()).compareTo(defaultString(theO2.getCode())); + } + return retVal; + } + }); + thePublished.clear(); + for (Coding next : tags) { + thePublished.add(next); + } + } + + + private void sortCodings(List theSecLabels) { + Collections.sort(theSecLabels, new Comparator() { + @Override + public int compare(Coding theO1, Coding theO2) { + return theO1.getSystemElement().getValue().compareTo(theO2.getSystemElement().getValue()); + } + }); + } + + private List sortIds(List theProfiles) { + ArrayList retVal = new ArrayList(theProfiles); + Collections.sort(retVal, new Comparator() { + @Override + public int compare(UriType theO1, UriType theO2) { + return theO1.getValue().compareTo(theO2.getValue()); + } + }); + return retVal; + } + + @Test + public void testCantSearchForDeletedResourceByLanguageOrTag() { + String methodName = "testCantSearchForDeletedResourceByLanguageOrTag"; + Organization org = new Organization(); + org.setLanguageElement(new CodeType("EN_ca")); + org.setName(methodName); + + ArrayList tl = new ArrayList(); + tl.add(new Coding().setSystem(methodName).setCode(methodName)); + org.getMeta().getTag().addAll(tl); + + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add("_language", new StringParam("EN_ca")); + assertEquals(1, myOrganizationDao.search(map).size().intValue()); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("_tag", new TokenParam(methodName, methodName)); + assertEquals(1, myOrganizationDao.search(map).size().intValue()); + + myOrganizationDao.delete(orgId, mySrd); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("_language", new StringParam("EN_ca")); + assertEquals(0, myOrganizationDao.search(map).size().intValue()); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("_tag", new TokenParam(methodName, methodName)); + assertEquals(0, myOrganizationDao.search(map).size().intValue()); + } + + @Test + public void testChoiceParamConcept() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01"); + o1.setValue(newCodeableConcept("testChoiceParam01CCS", "testChoiceParam01CCV")); + IIdType id1 = myObservationDao.create(o1, mySrd).getId(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_CONCEPT, new TokenParam("testChoiceParam01CCS", "testChoiceParam01CCV")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id1, found.getResources(0, 1).get(0).getIdElement()); + } + } + + @Test + public void testChoiceParamDate() { + Observation o2 = new Observation(); + o2.getCode().addCoding().setSystem("foo").setCode("testChoiceParam02"); + o2.setValue(new Period().setStartElement(new DateTimeType("2001-01-01")).setEndElement(new DateTimeType("2001-01-03"))); + IIdType id2 = myObservationDao.create(o2, mySrd).getId(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_DATE, new DateParam("2001")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id2, found.getResources(0, 1).get(0).getIdElement()); + } + } + + @Test + public void testChoiceParamDateAlt() { + Observation o2 = new Observation(); + o2.getCode().addCoding().setSystem("foo").setCode("testChoiceParamDateAlt02"); + o2.setEffective(new DateTimeType("2015-03-08T11:11:11")); + IIdType id2 = myObservationDao.create(o2, mySrd).getId(); + + { + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2001-01-02"))); + assertThat(found, hasItem(id2.getIdPartAsLong())); + } + { + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02"))); + assertThat(found, not(hasItem(id2.getIdPartAsLong()))); + } + } + + @Test + public void testChoiceParamDateEquals() { + Encounter enc = new Encounter(); + enc.getPeriod().setStartElement(new DateTimeType("2016-05-10")).setEndElement(new DateTimeType("2016-05-20")); + String id = myEncounterDao.create(enc, mySrd).getId().toUnqualifiedVersionless().getValue(); + + List ids; + + /* + * This should not match, per the definition of eq + */ + + ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(new SearchParameterMap(Encounter.SP_DATE, new DateParam("2016-05-15")).setLoadSynchronous(true))); + assertThat(ids, empty()); + + ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(new SearchParameterMap(Encounter.SP_DATE, new DateParam("eq2016-05-15")).setLoadSynchronous(true))); + assertThat(ids, empty()); + + // Should match + + ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(new SearchParameterMap(Encounter.SP_DATE, new DateParam("eq2016")).setLoadSynchronous(true))); + assertThat(ids, org.hamcrest.Matchers.contains(id)); + + ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(new SearchParameterMap(Encounter.SP_DATE, new DateParam("2016")).setLoadSynchronous(true))); + assertThat(ids, org.hamcrest.Matchers.contains(id)); + + } + + @Test + public void testChoiceParamDateRange() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParamDateRange01"); + o1.setEffective(new Period().setStartElement(new DateTimeType("2015-01-01T00:00:00Z")).setEndElement(new DateTimeType("2015-01-10T00:00:00Z"))); + IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getCode().addCoding().setSystem("foo").setCode("testChoiceParamDateRange02"); + o2.setEffective(new Period().setStartElement(new DateTimeType("2015-01-05T00:00:00Z")).setEndElement(new DateTimeType("2015-01-15T00:00:00Z"))); + IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_DATE, new DateParam("ge2015-01-02T00:00:00Z")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id1, id2)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_DATE, new DateParam("gt2015-01-02T00:00:00Z")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id1, id2)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_DATE, new DateParam("gt2015-01-10T00:00:00Z")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id2)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_DATE, new DateParam("sa2015-01-02T00:00:00Z")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id2)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_DATE, new DateParam("eb2015-01-13T00:00:00Z")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id1)); + } + } + + @Test + public void testChoiceParamQuantity() { + Observation o3 = new Observation(); + o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03"); + o3.setValue(new Quantity(QuantityComparator.GREATER_THAN, 123.0, "foo", "bar", "bar")); + IIdType id3 = myObservationDao.create(o3, mySrd).getId(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam(">100", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("gt100", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("<100", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(0, found.size().intValue()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("lt100", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(0, found.size().intValue()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0001", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(0, found.size().intValue()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("~120", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ap120", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("eq123", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("eq120", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(0, found.size().intValue()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ne120", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id3, found.getResources(0, 1).get(0).getIdElement()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ne123", "foo", "bar")).setLoadSynchronous(true)); + assertEquals(0, found.size().intValue()); + } + } + + @Test + public void testChoiceParamQuantityPrecision() { + Observation o3 = new Observation(); + o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03"); + o3.setValue(new Quantity(null, 123.01, "foo", "bar", "bar")); + IIdType id3 = myObservationDao.create(o3, mySrd).getId().toUnqualifiedVersionless(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id3)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id3)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id3)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.010", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder(id3)); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder()); + } + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true)); + List list = toUnqualifiedVersionlessIds(found); + assertThat(list, containsInAnyOrder()); + } + } + + @Test + public void testChoiceParamString() { + + Observation o4 = new Observation(); + o4.getCode().addCoding().setSystem("foo").setCode("testChoiceParam04"); + o4.setValue(new StringType("testChoiceParam04Str")); + IIdType id4 = myObservationDao.create(o4, mySrd).getId(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_STRING, new StringParam("testChoiceParam04Str")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id4, found.getResources(0, 1).get(0).getIdElement()); + } + } + + @Test + public void testCodeSystemCreateAndDelete() { + CodeSystem cs = new CodeSystem(); + cs.setStatus(PublicationStatus.DRAFT); + IIdType id = myCodeSystemDao.create(cs, mySrd).getId().toUnqualifiedVersionless(); + + myCodeSystemDao.delete(id, mySrd); + + assertGone(id.toUnqualifiedVersionless()); + } + + @Test + @Ignore + public void testCreateBuiltInProfiles() throws Exception { + org.hl7.fhir.r4.model.Bundle bundle; + String name = "profiles-resources"; + ourLog.info("Uploading " + name); + String vsContents; + vsContents = IOUtils.toString(FhirResourceDaoR4Test.class.getResourceAsStream("/org/hl7/fhir/r4/model/profile/" + name + ".xml"), "UTF-8"); + + bundle = myFhirCtx.newXmlParser().parseResource(org.hl7.fhir.r4.model.Bundle.class, vsContents); + for (BundleEntryComponent i : bundle.getEntry()) { + org.hl7.fhir.r4.model.Resource next = i.getResource(); + + ourLog.debug(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next)); + if (next instanceof StructureDefinition) { + myStructureDefinitionDao.update((StructureDefinition) next, mySrd); + } else if (next instanceof CompartmentDefinition) { + myCompartmentDefinitionDao.update((CompartmentDefinition) next, mySrd); + } else if (next instanceof OperationDefinition) { + myOperationDefinitionDao.update((OperationDefinition) next, mySrd); + } else { + fail(next.getClass().getName()); + } + + } + + } + + @Test + public void testCreateBundleAllowsDocumentAndCollection() { + String methodName = "testCreateBundleAllowsDocumentAndCollection"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType pid = myPatientDao.create(p, mySrd).getId(); + p.setId(pid); + ourLog.info("Created patient, got it: {}", pid); + + Bundle bundle = new Bundle(); + bundle.setType(null); + bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue()); + try { + myBundleDao.create(bundle, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage()); + } + + bundle = new Bundle(); + bundle.setType(BundleType.SEARCHSET); + bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue()); + try { + myBundleDao.create(bundle, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset", e.getMessage()); + } + + bundle = new Bundle(); + bundle.setType(BundleType.COLLECTION); + bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue()); + myBundleDao.create(bundle, mySrd); + + bundle = new Bundle(); + bundle.setType(BundleType.DOCUMENT); + bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue()); + myBundleDao.create(bundle, mySrd); + + } + + + @Test + public void testCreateDifferentTypesWithSameForcedId() { + String idName = "forcedId"; + + Patient pat = new Patient(); + pat.setId(idName); + pat.addName().setFamily("FAM"); + IIdType patId = myPatientDao.update(pat, mySrd).getId(); + assertEquals("Patient/" + idName, patId.toUnqualifiedVersionless().getValue()); + + Observation obs = new Observation(); + obs.setId(idName); + obs.getCode().addCoding().setSystem("foo").setCode("testCreateDifferentTypesWithSameForcedId"); + IIdType obsId = myObservationDao.update(obs, mySrd).getId(); + assertEquals("Observation/" + idName, obsId.toUnqualifiedVersionless().getValue()); + + pat = myPatientDao.read(patId.toUnqualifiedVersionless(), mySrd); + obs = myObservationDao.read(obsId.toUnqualifiedVersionless(), mySrd); + } + + + @Test + public void testCreateDuplicateTagsDoesNotCauseDuplicates() { + Patient p = new Patient(); + p.setActive(true); + + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + p.getMeta().addTag().setSystem("FOO").setCode("BAR"); + + myPatientDao.create(p); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + assertThat(myResourceTagDao.findAll(), hasSize(1)); + assertThat(myTagDefinitionDao.findAll(), hasSize(1)); + } + }); + + } + + @Test + public void testCreateEmptyTagsIsIgnored() { + Patient p = new Patient(); + p.setActive(true); + + // Add an empty tag + p.getMeta().addTag(); + + // Add another empty tag + p.getMeta().addTag().setSystem(""); + p.getMeta().addTag().setCode(""); + p.getMeta().addTag().setDisplay(""); + + myPatientDao.create(p); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + assertThat(myResourceTagDao.findAll(), empty()); + assertThat(myTagDefinitionDao.findAll(), empty()); + } + }); + + } + + @Test + public void testCreateLongString() { + //@formatter:off + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " "; + //@formatter:on + + NamingSystem res = myFhirCtx.newXmlParser().parseResource(NamingSystem.class, input); + IIdType id = myNamingSystemDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + assertThat(toUnqualifiedVersionlessIdValues(myNamingSystemDao.search(new SearchParameterMap(NamingSystem.SP_NAME, new StringParam("NDF")).setLoadSynchronous(true))), org.hamcrest.Matchers.contains(id.getValue())); + } + + @Test + public void testCreateNumericIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/9999999999999"); + try { + myPatientDao.update(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("clients may only assign IDs which contain at least one non-numeric")); + } + } + + @Test + public void testCreateOperationOutcome() { + /* + * If any of this ever fails, it means that one of the OperationOutcome issue severity codes has changed code value across versions. We store the string as a constant, so something will need to + * be fixed. + */ + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN); + assertEquals(org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); + } + + @Test + public void testCreateOperationOutcomeError() { + FhirResourceDaoR4 dao = new FhirResourceDaoR4(); + OperationOutcome oo = (OperationOutcome) dao.createErrorOperationOutcome("my message", "incomplete"); + assertEquals(IssueSeverity.ERROR.toCode(), oo.getIssue().get(0).getSeverity().toCode()); + assertEquals("my message", oo.getIssue().get(0).getDiagnostics()); + assertEquals(IssueType.INCOMPLETE, oo.getIssue().get(0).getCode()); + } + + @Test + public void testCreateOperationOutcomeInfo() { + FhirResourceDaoR4 dao = new FhirResourceDaoR4(); + OperationOutcome oo = (OperationOutcome) dao.createInfoOperationOutcome("my message"); + assertEquals(IssueSeverity.INFORMATION.toCode(), oo.getIssue().get(0).getSeverity().toCode()); + assertEquals("my message", oo.getIssue().get(0).getDiagnostics()); + assertEquals(IssueType.INFORMATIONAL, oo.getIssue().get(0).getCode()); + } + + @Test + public void testCreateReferenceToDeletedResource() { + Organization org = new Organization(); + org.setActive(true); + IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + + myOrganizationDao.delete(orgId); + + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(orgId); + try { + myPatientDao.create(p); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/" + orgId.getIdPart() + " is deleted, specified in path: Patient.managingOrganization", e.getMessage()); + } + } + + @Test + public void testCreateSummaryFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails"); + p.addName().setFamily("Hello"); + + p.getMeta().addTag().setSystem(Constants.TAG_SUBSETTED_SYSTEM).setCode(Constants.TAG_SUBSETTED_CODE); + + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("subsetted")); + } + } + + @Test + public void testCreateTextIdDoesntFail() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/ABC"); + String id = myPatientDao.create(p, mySrd).getId().getIdPart(); + assertNotEquals("ABC", id); + } + + @Test + public void testCreateWithIfNoneExistBasic() { + String methodName = "testCreateWithIfNoneExistBasic"; + MethodOutcome results; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + // Verify interceptor + ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); + ActionRequestDetails details = detailsCapt.getValue(); + assertNotNull(details.getId()); + assertEquals("Patient", details.getResourceType()); + assertEquals(Patient.class, details.getResource().getClass()); + + reset(myInterceptor); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + results = myPatientDao.create(p, "Patient?identifier=urn%3Asystem%7C" + methodName, mySrd); + assertEquals(id.getIdPart(), results.getId().getIdPart()); + assertFalse(results.getCreated().booleanValue()); + + verifyNoMoreInteractions(myInterceptor); + + // Now create a second one + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + results = myPatientDao.create(p, mySrd); + assertNotEquals(id.getIdPart(), results.getId().getIdPart()); + assertTrue(results.getCreated().booleanValue()); + + // Now try to create one with the original match URL and it should fail + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + try { + myPatientDao.create(p, "Patient?identifier=urn%3Asystem%7C" + methodName, mySrd); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("Failed to CREATE")); + } + + } + + @Test + public void testCreateWithIfNoneExistId() { + String methodName = "testCreateWithIfNoneExistId"; + MethodOutcome results; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualified(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + results = myPatientDao.create(p, "Patient?_id=" + id.toVersionless().getValue(), mySrd); + assertEquals(id.getIdPart(), results.getId().getIdPart()); + assertEquals(id.getVersionIdPart(), results.getId().getVersionIdPart()); + assertFalse(results.getCreated().booleanValue()); + + } + + @Test + public void testCreateWithIllegalReference() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01"); + IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + try { + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(id1); + myPatientDao.create(p, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Invalid reference found at path 'Patient.managingOrganization'. Resource type 'Observation' is not valid for this path", e.getMessage()); + } + + try { + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(new IdType("Organization", id1.getIdPart())); + myPatientDao.create(p, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage()); + } + + // Now with a forced ID + + o1 = new Observation(); + o1.setId("testCreateWithIllegalReference"); + o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01"); + id1 = myObservationDao.update(o1, mySrd).getId().toUnqualifiedVersionless(); + + try { + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(id1); + myPatientDao.create(p, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Invalid reference found at path 'Patient.managingOrganization'. Resource type 'Observation' is not valid for this path", e.getMessage()); + } + + try { + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(new IdType("Organization", id1.getIdPart())); + myPatientDao.create(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/testCreateWithIllegalReference not found, specified in path: Patient.managingOrganization", e.getMessage()); + } + + } + + @Test + public void testCreateWithInvalid() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01"); + o1.setValue(newCodeableConcept("testChoiceParam01CCS", "testChoiceParam01CCV")); + IIdType id1 = myObservationDao.create(o1, mySrd).getId(); + + { + IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_CONCEPT, new TokenParam("testChoiceParam01CCS", "testChoiceParam01CCV")).setLoadSynchronous(true)); + assertEquals(1, found.size().intValue()); + assertEquals(id1, found.getResources(0, 1).get(0).getIdElement()); + } + } + + + @Test + public void testCreateWithInvalidReferenceFailsGracefully() { + Patient patient = new Patient(); + patient.addName().setFamily("testSearchResourceLinkWithChainWithMultipleTypes01"); + patient.setManagingOrganization(new Reference("Organization/99999999")); + try { + myPatientDao.create(patient, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), StringContains.containsString("99999 not found")); + } + + } + + @Test + public void testCreateWithInvalidReferenceNoId() { + Patient p = new Patient(); + p.addName().setFamily("Hello"); + p.getManagingOrganization().setReference("Organization/"); + + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Does not contain resource ID")); + } + } + + @Test + public void testCreateWithReferenceBadType() { + Patient p = new Patient(); + p.addName().setFamily("Hello"); + p.getManagingOrganization().setReference("Blah/123"); + + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Invalid reference found at path 'Patient.managingOrganization'. Resource type 'Blah' is not valid for this path")); + } + } + + @Test + public void testCreateWithReferenceNoType() { + Patient p = new Patient(); + p.addName().setFamily("Hello"); + p.getManagingOrganization().setReference("123"); + + try { + myPatientDao.create(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Does not contain resource type")); + } + } + + @Test + public void testCreateWrongType() { + + // Lose typing so we can put the wrong type in + @SuppressWarnings("rawtypes") + IFhirResourceDao dao = myNamingSystemDao; + + Patient resource = new Patient(); + resource.addName().setFamily("My Name"); + try { + dao.create(resource, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Incorrect resource type detected for endpoint, found Patient but expected NamingSystem", e.getMessage()); + } + } + + @Test + public void testDatePeriodParamEndOnly() { + { + Encounter enc = new Encounter(); + enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02"); + enc.getPeriod().getEndElement().setValueAsString("2001-01-02"); + myEncounterDao.create(enc, mySrd); + } + SearchParameterMap params; + List encs; + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02")); + // encs = toList(ourEncounterDao.search(params)); + // assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDatePeriodParamStartAndEnd() { + { + Encounter enc = new Encounter(); + enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03"); + enc.getPeriod().getStartElement().setValueAsString("2001-01-02"); + enc.getPeriod().getEndElement().setValueAsString("2001-01-03"); + myEncounterDao.create(enc, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + List encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDatePeriodParamStartOnly() { + { + Encounter enc = new Encounter(); + enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01"); + enc.getPeriod().getStartElement().setValueAsString("2001-01-02"); + myEncounterDao.create(enc, mySrd); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01")); + List encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01")); + encs = toList(myEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); + params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01")); + encs = toList(myEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDeleteFailsIfIncomingLinks() { + String methodName = "testDeleteFailsIfIncomingLinks"; + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + patient.getManagingOrganization().setReferenceElement(orgId); + IIdType patId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add("_id", new StringParam(orgId.getIdPart())); + map.addRevInclude(new Include("*")); + List found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); + assertThat(found, contains(orgId, patId)); + + try { + myOrganizationDao.delete(orgId, mySrd); + fail(); + } catch (ResourceVersionConflictException e) { + assertConflictException(e); + } + + myPatientDao.delete(patId, mySrd); + + map = new SearchParameterMap(); + map.add("_id", new StringParam(orgId.getIdPart())); + map.addRevInclude(new Include("*")); + found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); + assertThat(found, contains(orgId)); + + myOrganizationDao.delete(orgId, mySrd); + + map = new SearchParameterMap(); + map.add("_id", new StringParam(orgId.getIdPart())); + map.addRevInclude(new Include("*")); + found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); + assertThat(found, empty()); + + } + + @Test + public void testDeleteResource() { + int initialHistory = myPatientDao.history((Date) null, null, mySrd).size(); + + IIdType id1; + IIdType id2; + IIdType id2b; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_testDeleteResource").addGiven("Joe"); + id1 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testDeleteResource").addGiven("John"); + id2 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = myPatientDao.read(id2, mySrd); + patient.addIdentifier().setSystem("ZZZZZZZ").setValue("ZZZZZZZZZ"); + id2b = myPatientDao.update(patient, mySrd).getId(); + } + ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b }); + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(Patient.SP_FAMILY, new StringParam("Tester_testDeleteResource")); + List patients = toList(myPatientDao.search(params)); + assertEquals(2, patients.size()); + + myPatientDao.delete(id1, mySrd); + + patients = toList(myPatientDao.search(params)); + assertEquals(1, patients.size()); + + myPatientDao.read(id1, mySrd); + try { + myPatientDao.read(id1.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(4 + initialHistory, history.size().intValue()); + List resources = history.getResources(0, 4); + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) resources.get(0))); + + try { + myPatientDao.delete(id2, mySrd); + fail(); + } catch (ResourceVersionConflictException e) { + // good + } + + myPatientDao.delete(id2.toVersionless(), mySrd); + + patients = toList(myPatientDao.search(params)); + assertEquals(0, patients.size()); + + } + + @Test + public void testDeleteThenUndelete() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_testDeleteThenUndelete").addGiven("Joe"); + IIdType id = myPatientDao.create(patient, mySrd).getId(); + assertThat(id.getValue(), Matchers.endsWith("/_history/1")); + + // should be ok + myPatientDao.read(id.toUnqualifiedVersionless(), mySrd); + + // Delete it + myPatientDao.delete(id.toUnqualifiedVersionless(), mySrd); + + try { + myPatientDao.read(id.toUnqualifiedVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // expected + } + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester_testDeleteThenUndelete").addGiven("Joe"); + patient.setId(id.toUnqualifiedVersionless()); + IIdType id2 = myPatientDao.update(patient, mySrd).getId(); + + assertThat(id2.getValue(), org.hamcrest.Matchers.endsWith("/_history/3")); + + IIdType gotId = myPatientDao.read(id.toUnqualifiedVersionless(), mySrd).getIdElement(); + assertEquals(id2, gotId); + } + + @Test + public void testDeleteWithHas() { + Observation obs1 = new Observation(); + obs1.setStatus(ObservationStatus.FINAL); + IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless(); + + DiagnosticReport rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + rpt.addResult(new Reference(obs2id)); + myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless(); + + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + + try { + myObservationDao.deleteByUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER", mySrd); + fail(); + } catch (ResourceVersionConflictException e) { + assertConflictException(e); + } + + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + } + + @Test + public void testDeleteWithMatchUrl() { + String methodName = "testDeleteWithMatchUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + myPatientDao.deleteByUrl("Patient?identifier=urn%3Asystem%7C" + methodName, mySrd); + + try { + myPatientDao.read(id.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // ok + } + + try { + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + // ok + } + + IBundleProvider history = myPatientDao.history(id, null, null, mySrd); + assertEquals(2, history.size().intValue()); + + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(0, 1).get(0))); + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(0, 1).get(0)).getValue()); + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(1, 2).get(0))); + + } + + @Test + public void testDeleteWithMatchUrlChainedIdentifier() { + String methodName = "testDeleteWithMatchUrlChainedIdentifer"; + + Organization org = new Organization(); + org.setName(methodName); + org.addIdentifier().setSystem("http://example.com").setValue(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.getManagingOrganization().setReferenceElement(orgId); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info("Created patient, got it: {}", id); + + myPatientDao.deleteByUrl("Patient?organization.identifier=http://example.com|" + methodName, mySrd); + assertGone(id); + assertNotGone(orgId); + + myOrganizationDao.deleteByUrl("Organization?identifier=http://example.com|" + methodName, mySrd); + assertGone(id); + assertGone(orgId); + + } + + @Test + public void testDeleteWithMatchUrlChainedProfile() { + String methodName = "testDeleteWithMatchUrlChainedProfile"; + + Organization org = new Organization(); + + org.getMeta().getProfile().add(new IdType("http://foo")); + org.setName(methodName); + + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.getManagingOrganization().setReferenceElement(orgId); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info("Created patient, got it: {}", id); + + myPatientDao.deleteByUrl("Patient?organization._profile=http://foo", mySrd); + assertGone(id); + + myOrganizationDao.deleteByUrl("Organization?_profile=http://foo", mySrd); + try { + myOrganizationDao.read(orgId, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myPatientDao.deleteByUrl("Patient?organization._profile.identifier=http://foo", mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid parameter chain: organization._profile.identifier", e.getMessage()); + } + + try { + myOrganizationDao.deleteByUrl("Organization?_profile.identifier=http://foo", mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid parameter chain: _profile.identifier", e.getMessage()); + } + + } + + @Test + public void testDeleteWithMatchUrlChainedString() { + String methodName = "testDeleteWithMatchUrlChainedString"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.getManagingOrganization().setReferenceElement(orgId); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info("Created patient, got it: {}", id); + + myPatientDao.deleteByUrl("Patient?organization.name=" + methodName, mySrd); + + assertGone(id); + } + + @Test + public void testDeleteWithMatchUrlChainedTag() { + String methodName = "testDeleteWithMatchUrlChainedString"; + + Organization org = new Organization(); + org.getMeta().addTag().setSystem("http://foo").setCode("term"); + + org.setName(methodName); + + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.getManagingOrganization().setReferenceElement(orgId); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info("Created patient, got it: {}", id); + + myPatientDao.deleteByUrl("Patient?organization._tag=http://foo|term", mySrd); + assertGone(id); + + myOrganizationDao.deleteByUrl("Organization?_tag=http://foo|term", mySrd); + try { + myOrganizationDao.read(orgId, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myPatientDao.deleteByUrl("Patient?organization._tag.identifier=http://foo|term", mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid parameter chain: organization._tag.identifier", e.getMessage()); + } + + try { + myOrganizationDao.deleteByUrl("Organization?_tag.identifier=http://foo|term", mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid parameter chain: _tag.identifier", e.getMessage()); + } + + } + + @Test + public void testDeleteWithMatchUrlQualifierMissing() { + String methodName = "testDeleteWithMatchUrlChainedProfile"; + + /* + * Org 2 has no name + */ + + Organization org1 = new Organization(); + org1.addIdentifier().setValue(methodName); + IIdType org1Id = myOrganizationDao.create(org1, mySrd).getId().toUnqualifiedVersionless(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + p1.getManagingOrganization().setReferenceElement(org1Id); + IIdType patId1 = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); + + /* + * Org 2 has a name + */ + + Organization org2 = new Organization(); + org2.setName(methodName); + org2.addIdentifier().setValue(methodName); + IIdType org2Id = myOrganizationDao.create(org2, mySrd).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue(methodName); + p2.getManagingOrganization().setReferenceElement(org2Id); + IIdType patId2 = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless(); + + ourLog.info("Pat ID 1 : {}", patId1); + ourLog.info("Org ID 1 : {}", org1Id); + ourLog.info("Pat ID 2 : {}", patId2); + ourLog.info("Org ID 2 : {}", org2Id); + + myPatientDao.deleteByUrl("Patient?organization.name:missing=true", mySrd); + assertGone(patId1); + assertNotGone(patId2); + assertNotGone(org1Id); + assertNotGone(org2Id); + + myOrganizationDao.deleteByUrl("Organization?name:missing=true", mySrd); + assertGone(patId1); + assertNotGone(patId2); + assertGone(org1Id); + assertNotGone(org2Id); + + myPatientDao.deleteByUrl("Patient?organization.name:missing=false", mySrd); + assertGone(patId1); + assertGone(patId2); + assertGone(org1Id); + assertNotGone(org2Id); + + myOrganizationDao.deleteByUrl("Organization?name:missing=false", mySrd); + assertGone(patId1); + assertGone(patId2); + assertGone(org1Id); + assertGone(org2Id); + } + + @Test + public void testHistoryByForcedId() { + IIdType idv1; + IIdType idv2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testHistoryByForcedId"); + patient.addName().setFamily("Tester").addGiven("testHistoryByForcedId"); + patient.setId("Patient/testHistoryByForcedId"); + idv1 = myPatientDao.update(patient, mySrd).getId(); + + patient.addName().setFamily("Tester").addGiven("testHistoryByForcedIdName2"); + patient.setId(patient.getIdElement().toUnqualifiedVersionless()); + idv2 = myPatientDao.update(patient, mySrd).getId(); + } + + List patients = toList(myPatientDao.history(idv1.toVersionless(), null, null, mySrd)); + assertTrue(patients.size() == 2); + // Newest first + assertEquals("Patient/testHistoryByForcedId/_history/2", patients.get(0).getIdElement().toUnqualified().getValue()); + assertEquals("Patient/testHistoryByForcedId/_history/1", patients.get(1).getIdElement().toUnqualified().getValue()); + assertNotEquals(idv1, idv2); + } + + @Test + public void testHistoryOverMultiplePages() throws Exception { + String methodName = "testHistoryOverMultiplePages"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Date middleDate = null; + int halfSize = 50; + int fullSize = 100; + for (int i = 0; i < fullSize; i++) { + ourLog.info("Pass {}", i); + if (i == halfSize) { + Thread.sleep(fullSize); + middleDate = new Date(); + Thread.sleep(fullSize); + } + patient.setId(id); + patient.getName().get(0).getFamilyElement().setValue(methodName + "_i" + i); + myPatientDao.update(patient, mySrd); + } + + // By instance + IBundleProvider history = myPatientDao.history(id, null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By type + history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By server + history = mySystemDao.history(null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + /* + * With since date + */ + + // By instance + history = myPatientDao.history(id, middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By type + history = myPatientDao.history(middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By server + history = mySystemDao.history(middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + /* + * Now delete the most recent version and make sure everything still works + */ + + myPatientDao.delete(id.toVersionless(), mySrd); + + fullSize++; + halfSize++; + + // By instance + history = myPatientDao.history(id, null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By type + history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By server + history = mySystemDao.history(null, null, mySrd); + assertEquals(fullSize + 1, history.size().intValue()); + for (int i = 0; i < fullSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + /* + * With since date + */ + + // By instance + history = myPatientDao.history(id, middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By type + history = myPatientDao.history(middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + // By server + history = mySystemDao.history(middleDate, null, mySrd); + assertEquals(halfSize, history.size().intValue()); + for (int i = 0; i < halfSize; i++) { + String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); + String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue(); + assertEquals(expected, actual); + } + + } + + @Test + public void testHistoryReflectsMetaOperations() throws Exception { + Patient inPatient = new Patient(); + inPatient.addName().setFamily("version1"); + inPatient.getMeta().addProfile("http://example.com/1"); + IIdType id = myPatientDao.create(inPatient, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(1, history.size().intValue()); + Patient outPatient = (Patient) history.getResources(0, 1).get(0); + assertEquals("version1", inPatient.getName().get(0).getFamily()); + List profiles = toStringList(outPatient.getMeta().getProfile()); + assertThat(profiles, org.hamcrest.Matchers.contains("http://example.com/1")); + + /* + * Change metadata + */ + + inPatient.getMeta().addProfile("http://example.com/2"); + myPatientDao.metaAddOperation(id, inPatient.getMeta(), mySrd); + + history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(1, history.size().intValue()); + outPatient = (Patient) history.getResources(0, 1).get(0); + assertEquals("version1", inPatient.getName().get(0).getFamily()); + profiles = toStringList(outPatient.getMeta().getProfile()); + assertThat(profiles, containsInAnyOrder("http://example.com/1", "http://example.com/2")); + + /* + * Do an update + */ + + inPatient.setId(id); + inPatient.getMeta().addProfile("http://example.com/3"); + inPatient.getName().get(0).setFamily("version2"); + myPatientDao.update(inPatient, mySrd); + + history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(2, history.size().intValue()); + outPatient = (Patient) history.getResources(0, 2).get(0); + assertEquals("version2", outPatient.getName().get(0).getFamily()); + profiles = toStringList(outPatient.getMeta().getProfile()); + ourLog.info(profiles.toString()); + assertThat(profiles, containsInAnyOrder("http://example.com/1", "http://example.com/2", "http://example.com/3")); + + outPatient = (Patient) history.getResources(0, 2).get(1); + assertEquals("version1", outPatient.getName().get(0).getFamily()); + profiles = toStringList(outPatient.getMeta().getProfile()); + assertThat(profiles, containsInAnyOrder("http://example.com/1", "http://example.com/2")); + } + + @Test + public void testHistoryWithDeletedResource() throws Exception { + String methodName = "testHistoryWithDeletedResource"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + myPatientDao.delete(id, mySrd); + patient.setId(id); + myPatientDao.update(patient, mySrd); + + IBundleProvider history = myPatientDao.history(id, null, null, mySrd); + List entries = history.getResources(0, 3); + ourLog.info(((IAnyResource) entries.get(0)).getIdElement() + " - " + ((IAnyResource) entries.get(0)).getMeta().getLastUpdated()); + ourLog.info(((IAnyResource) entries.get(1)).getIdElement() + " - " + ((IAnyResource) entries.get(1)).getMeta().getLastUpdated()); + ourLog.info(((IAnyResource) entries.get(2)).getIdElement() + " - " + ((IAnyResource) entries.get(2)).getMeta().getLastUpdated()); + assertEquals(3, history.size().intValue()); + + assertEquals(id.withVersion("3"), entries.get(0).getIdElement()); + assertEquals(id.withVersion("2"), entries.get(1).getIdElement()); + assertEquals(id.withVersion("1"), entries.get(2).getIdElement()); + + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) entries.get(0))); + assertEquals(BundleEntryTransactionMethodEnum.PUT.getCode(), ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) entries.get(0))); + + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) entries.get(1))); + assertEquals(BundleEntryTransactionMethodEnum.DELETE.getCode(), ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) entries.get(1))); + + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) entries.get(2))); + assertEquals(BundleEntryTransactionMethodEnum.POST.getCode(), ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) entries.get(2))); + } + + @Test + public void testHistoryWithFromAndTo() throws Exception { + String methodName = "testHistoryWithFromAndTo"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + List preDates = Lists.newArrayList(); + List ids = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + preDates.add(new Date()); + Thread.sleep(100); + patient.setId(id); + patient.getName().get(0).getFamilyElement().setValue(methodName + "_i"+i); + ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue()); + } + + List idValues; + + idValues = toUnqualifiedIdValues(myPatientDao.history(id, preDates.get(0), preDates.get(3), mySrd)); + assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + + idValues = toUnqualifiedIdValues(myPatientDao.history(preDates.get(0), preDates.get(3), mySrd)); + assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + + idValues = toUnqualifiedIdValues(mySystemDao.history(preDates.get(0), preDates.get(3), mySrd)); + assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + } + + @Test + public void testHistoryWithFutureSinceDate() throws Exception { + + Date before = new Date(); + Thread.sleep(10); + + Patient inPatient = new Patient(); + inPatient.addName().setFamily("version1"); + inPatient.getMeta().addProfile("http://example.com/1"); + myPatientDao.create(inPatient, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(10); + Date after = new Date(); + + // No since + + IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + assertEquals(1, history.size().intValue()); + Patient outPatient = (Patient) history.getResources(0, 1).get(0); + assertEquals("version1", inPatient.getName().get(0).getFamily()); + List profiles = toStringList(outPatient.getMeta().getProfile()); + assertThat(profiles, org.hamcrest.Matchers.contains("http://example.com/1")); + + // Before since + + history = myPatientDao.history(before, null, mySrd); + assertEquals(1, history.size().intValue()); + outPatient = (Patient) history.getResources(0, 1).get(0); + assertEquals("version1", inPatient.getName().get(0).getFamily()); + profiles = toStringList(outPatient.getMeta().getProfile()); + assertThat(profiles, org.hamcrest.Matchers.contains("http://example.com/1")); + + // After since + + history = myPatientDao.history(after, null, mySrd); + assertEquals(0, history.size().intValue()); + + } + + @Test + public void testHistoryWithInvalidId() throws Exception { + try { + myPatientDao.history(new IdType("Patient/FOOFOOFOO"), null, null, mySrd); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Resource Patient/FOOFOOFOO is not known", e.getMessage()); + } + } + + @Test + public void testIdParam() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + + MethodOutcome outcome = myPatientDao.create(patient, mySrd); + assertNotNull(outcome.getId()); + assertFalse(outcome.getId().isEmpty()); + + Date now = new Date(); + + { + Patient retrieved = myPatientDao.read(outcome.getId(), mySrd); + Date published = retrieved.getMeta().getLastUpdated(); + assertTrue(published.before(now)); + } + + /* + * This ID points to a patient, so we should not be able to return othe types with it + */ + try { + myEncounterDao.read(outcome.getId(), mySrd); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + try { + myEncounterDao.read(new IdType(outcome.getId().getIdPart()), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + // expected + } + + // Now search by _id + { + SearchParameterMap paramMap = new SearchParameterMap(); + paramMap.add("_id", new StringParam(outcome.getId().getIdPart())); + List ret = toList(myPatientDao.search(paramMap)); + assertEquals(1, ret.size()); + Patient p = ret.get(0); + assertEquals("Tester", p.getName().get(0).getFamily()); + } + { + SearchParameterMap paramMap = new SearchParameterMap(); + paramMap.add("_id", new StringParam(outcome.getId().getIdPart())); + paramMap.add(Patient.SP_NAME, new StringParam("tester")); + List ret = toList(myPatientDao.search(paramMap)); + assertEquals(1, ret.size()); + Patient p = ret.get(0); + assertEquals("Tester", p.getName().get(0).getFamily()); + } + { + SearchParameterMap paramMap = new SearchParameterMap(); + paramMap.add(Patient.SP_NAME, new StringParam("tester")); + paramMap.add("_id", new StringParam(outcome.getId().getIdPart())); + List ret = toList(myPatientDao.search(paramMap)); + assertEquals(1, ret.size()); + Patient p = ret.get(0); + assertEquals("Tester", p.getName().get(0).getFamily()); + } + { + SearchParameterMap paramMap = new SearchParameterMap(); + paramMap.add(Patient.SP_NAME, new StringParam("tester")); + paramMap.add("_id", new StringParam("000")); + List ret = toList(myPatientDao.search(paramMap)); + assertEquals(0, ret.size()); + } + } + + @Test + public void testIndexConditionWithAllOnsetTypes() { + // DateTimeType.class, Age.class, Period.class, Range.class, StringType.class + + Condition c0 = new Condition(); + c0.setOnset(new DateTimeType("2011-01-01")); + myConditionDao.create(c0, mySrd).getId().toUnqualifiedVersionless(); + + Condition c1 = new Condition(); + c1.setOnset(new Age().setValue(100L).setCode("AGECODE")); + myConditionDao.create(c1, mySrd).getId().toUnqualifiedVersionless(); + + Condition c2 = new Condition(); + c2.setOnset(new Period().setStart(new Date()).setEnd(new Date())); + myConditionDao.create(c2, mySrd).getId().toUnqualifiedVersionless(); + + Condition c3 = new Condition(); + c3.setOnset(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(200L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(300L))); + myConditionDao.create(c3, mySrd).getId().toUnqualifiedVersionless(); + + Condition c4 = new Condition(); + c4.setOnset(new StringType("FOO")); + myConditionDao.create(c4, mySrd).getId().toUnqualifiedVersionless(); + } + + @Test + public void testInstanceMetaOperations() { + String methodName = "testMetaRead"; + IIdType id; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.getMeta().addTag("tag_scheme1", "tag_code1", "tag_display1"); + patient.getMeta().addTag("tag_scheme2", "tag_code2", "tag_display2"); + + patient.getMeta().addSecurity().setSystem("seclabel_sys1").setCode("seclabel_code1").setDisplay("seclabel_dis1"); + patient.getMeta().addSecurity().setSystem("seclabel_sys2").setCode("seclabel_code2").setDisplay("seclabel_dis2"); + + patient.getMeta().addProfile(("http://profile/1")); + patient.getMeta().addProfile(("http://profile/2")); + + id = myPatientDao.create(patient, mySrd).getId(); + } + + assertTrue(id.hasVersionIdPart()); + + /* + * Create a second version + */ + + Patient pt = myPatientDao.read(id, mySrd); + pt.addName().setFamily("anotherName"); + myPatientDao.update(pt, mySrd); + + /* + * Meta-Delete on previous version + */ + + Meta meta = new Meta(); + meta.addTag().setSystem("tag_scheme1").setCode("tag_code1"); + meta.addProfile("http://profile/1"); + meta.addSecurity().setSystem("seclabel_sys1").setCode("seclabel_code1"); + Meta newMeta = myPatientDao.metaDeleteOperation(id.withVersion("1"), meta, mySrd); + assertEquals(1, newMeta.getProfile().size()); + assertEquals(1, newMeta.getSecurity().size()); + assertEquals(1, newMeta.getTag().size()); + assertEquals("tag_code2", newMeta.getTag().get(0).getCode()); + assertEquals("http://profile/2", newMeta.getProfile().get(0).getValue()); + assertEquals("seclabel_code2", newMeta.getSecurity().get(0).getCode()); + + /* + * Meta Read on Version + */ + + meta = myPatientDao.metaGetOperation(Meta.class, id.withVersion("1"), mySrd); + assertEquals(1, meta.getProfile().size()); + assertEquals(1, meta.getSecurity().size()); + assertEquals(1, meta.getTag().size()); + assertEquals("tag_code2", meta.getTag().get(0).getCode()); + assertEquals("http://profile/2", meta.getProfile().get(0).getValue()); + assertEquals("seclabel_code2", meta.getSecurity().get(0).getCode()); + + /* + * Meta-read on Version 2 + */ + meta = myPatientDao.metaGetOperation(Meta.class, id.withVersion("2"), mySrd); + assertEquals(2, meta.getProfile().size()); + assertEquals(2, meta.getSecurity().size()); + assertEquals(2, meta.getTag().size()); + + /* + * Meta-read on latest version + */ + meta = myPatientDao.metaGetOperation(Meta.class, id.toVersionless(), mySrd); + assertEquals(2, meta.getProfile().size()); + assertEquals(2, meta.getSecurity().size()); + assertEquals(2, meta.getTag().size()); + assertEquals("2", meta.getVersionId()); + + /* + * Meta-Add on previous version + */ + + meta = new Meta(); + meta.addTag().setSystem("tag_scheme1").setCode("tag_code1"); + meta.addProfile("http://profile/1"); + meta.addSecurity().setSystem("seclabel_sys1").setCode("seclabel_code1"); + newMeta = myPatientDao.metaAddOperation(id.withVersion("1"), meta, mySrd); + assertEquals(2, newMeta.getProfile().size()); + assertEquals(2, newMeta.getSecurity().size()); + assertEquals(2, newMeta.getTag().size()); + + /* + * Meta Read on Version + */ + + meta = myPatientDao.metaGetOperation(Meta.class, id.withVersion("1"), mySrd); + assertEquals(2, meta.getProfile().size()); + assertEquals(2, meta.getSecurity().size()); + assertEquals(2, meta.getTag().size()); + assertEquals("1", meta.getVersionId()); + + /* + * Meta delete on latest + */ + + meta = new Meta(); + meta.addTag().setSystem("tag_scheme1").setCode("tag_code1"); + meta.addProfile("http://profile/1"); + meta.addSecurity().setSystem("seclabel_sys1").setCode("seclabel_code1"); + newMeta = myPatientDao.metaDeleteOperation(id.toVersionless(), meta, mySrd); + assertEquals(1, newMeta.getProfile().size()); + assertEquals(1, newMeta.getSecurity().size()); + assertEquals(1, newMeta.getTag().size()); + assertEquals("tag_code2", newMeta.getTag().get(0).getCode()); + assertEquals("http://profile/2", newMeta.getProfile().get(0).getValue()); + assertEquals("seclabel_code2", newMeta.getSecurity().get(0).getCode()); + + /* + * Meta-Add on latest version + */ + + meta = new Meta(); + meta.addTag().setSystem("tag_scheme1").setCode("tag_code1"); + meta.addProfile("http://profile/1"); + meta.addSecurity().setSystem("seclabel_sys1").setCode("seclabel_code1"); + newMeta = myPatientDao.metaAddOperation(id.toVersionless(), meta, mySrd); + assertEquals(2, newMeta.getProfile().size()); + assertEquals(2, newMeta.getSecurity().size()); + assertEquals(2, newMeta.getTag().size()); + assertEquals("2", newMeta.getVersionId()); + + } + + /** + * See #196 + */ + @Test + public void testInvalidChainNames() { + ReferenceParam param = null; + + // OK + param = new ReferenceParam("999999999999"); + param.setChain("organization"); + myLocationDao.search(new SearchParameterMap("partof", param).setLoadSynchronous(true)); + + // OK + param = new ReferenceParam("999999999999"); + param.setChain("organization.name"); + myLocationDao.search(new SearchParameterMap("partof", param).setLoadSynchronous(true)); + + try { + param = new ReferenceParam("999999999999"); + param.setChain("foo"); + myLocationDao.search(new SearchParameterMap("partof", param).setLoadSynchronous(true)); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Invalid parameter chain: partof." + param.getChain())); + } + + try { + param = new ReferenceParam("999999999999"); + param.setChain("organization.foo"); + myLocationDao.search(new SearchParameterMap("partof", param).setLoadSynchronous(true)); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Invalid parameter chain: " + param.getChain())); + } + + try { + param = new ReferenceParam("999999999999"); + param.setChain("organization.name.foo"); + myLocationDao.search(new SearchParameterMap("partof", param).setLoadSynchronous(true)); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Invalid parameter chain: " + param.getChain())); + } + } + + /** + * See #534 + */ + @Test + public void testLogicalReferencesAreSearchable() { + myDaoConfig.setTreatReferencesAsLogical(null); + myDaoConfig.addTreatReferencesAsLogical("http://foo.com/identifier*"); + + Patient p1 = new Patient(); + p1.getManagingOrganization().setReference("http://foo.com/identifier/1"); + String p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Patient p2 = new Patient(); + p2.getManagingOrganization().setReference("http://foo.com/identifier/2"); + String p2id = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + IBundleProvider found = myPatientDao.search(new SearchParameterMap(Patient.SP_ORGANIZATION, new ReferenceParam("http://foo.com/identifier/1")).setLoadSynchronous(true)); + assertThat(toUnqualifiedVersionlessIdValues(found), org.hamcrest.Matchers.contains(p1id)); + assertThat(toUnqualifiedVersionlessIdValues(found), org.hamcrest.Matchers.not(org.hamcrest.Matchers.contains(p2id))); + } + + @Test + public void testOrganizationName() { + + //@formatter:off + String inputStr = + "{" + + " \"resourceType\":\"Organization\",\n" + + " \"extension\":[\n" + + " {\n" + + " \"url\":\"http://fhir.connectinggta.ca/Profile/organization#providerIdPool\",\n" + + " \"valueUri\":\"urn:oid:2.16.840.1.113883.3.239.23.21.1\"\n" + + " }\n" + + " ],\n" + + " \"text\":{\n" + + " \"status\":\"empty\",\n" + + " \"div\":\"
No narrative template available for resource profile: http://fhir.connectinggta.ca/Profile/organization
\"\n" + + " },\n" + + " \"identifier\":[\n" + + " {\n" + + " \"use\":\"official\",\n" + + " \"system\":\"urn:cgta:hsp_ids\",\n" + + " \"value\":\"urn:oid:2.16.840.1.113883.3.239.23.21\"\n" + + " }\n" + + " ],\n" + + " \"name\":\"Peterborough Regional Health Centre\"\n" + + "}\n"; + //@formatter:on + + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P"))); + int initial = val.size(); + + Organization org = myFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); + myOrganizationDao.create(org, mySrd); + + val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P"))); + assertEquals(initial + 1, val.size()); + + } + + @Test + public void testPersistContactPoint() { + List found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_TELECOM, new TokenParam(null, "555-123-4567")).setLoadSynchronous(true))); + int initialSize2000 = found.size(); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testPersistContactPoint"); + patient.addTelecom().setValue("555-123-4567"); + myPatientDao.create(patient, mySrd); + + found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_TELECOM, new TokenParam(null, "555-123-4567")).setLoadSynchronous(true))); + assertEquals(1 + initialSize2000, found.size()); + + } + + @Test + public void testPersistResourceLink() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testPersistResourceLink01"); + IIdType patientId01 = myPatientDao.create(patient, mySrd).getId(); + + Patient patient02 = new Patient(); + patient02.addIdentifier().setSystem("urn:system").setValue("testPersistResourceLink02"); + IIdType patientId02 = myPatientDao.create(patient02, mySrd).getId(); + + Observation obs01 = new Observation(); + obs01.setEffective(new DateTimeType(new Date())); + obs01.setSubject(new Reference(patientId01)); + IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId(); + + Observation obs02 = new Observation(); + obs02.setEffective(new DateTimeType(new Date())); + obs02.setSubject(new Reference(patientId02)); + IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId(); + + // Create another type, that shouldn't be returned + DiagnosticReport dr01 = new DiagnosticReport(); + dr01.setSubject(new Reference(patientId01)); + IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); + + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[] { patientId01, patientId02, obsId01, obsId02, drId01 }); + + List result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam(patientId01.getIdPart())).setLoadSynchronous(true))); + assertEquals(1, result.size()); + assertEquals(obsId01.getIdPart(), result.get(0).getIdElement().getIdPart()); + + result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam(patientId02.getIdPart())).setLoadSynchronous(true))); + assertEquals(1, result.size()); + assertEquals(obsId02.getIdPart(), result.get(0).getIdElement().getIdPart()); + + result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam("999999999999")).setLoadSynchronous(true)));; + assertEquals(0, result.size()); + + } + + @Test + public void testPersistSearchParamDate() { + List found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE, new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01")).setLoadSynchronous(true))); + int initialSize2000 = found.size(); + + found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE, new DateParam(ParamPrefixEnum.GREATERTHAN, "2002-01-01")).setLoadSynchronous(true))); + int initialSize2002 = found.size(); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.setBirthDateElement(new DateType("2001-01-01")); + + myPatientDao.create(patient, mySrd); + + found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE, new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01")).setLoadSynchronous(true))); + assertEquals(1 + initialSize2000, found.size()); + + found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE, new DateParam(ParamPrefixEnum.GREATERTHAN, "2002-01-01")).setLoadSynchronous(true))); + assertEquals(initialSize2002, found.size()); + + // If this throws an exception, that would be an acceptable outcome as well.. + try { + found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE + "AAAA", new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01")).setLoadSynchronous(true))); + assertEquals(0, found.size()); + } catch (InvalidRequestException e) { + assertEquals("Unknown search parameter birthdateAAAA for resource type Patient", e.getMessage()); + } + } + + @Test + public void testPersistSearchParamObservationString() { + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("testPersistSearchParamQuantity"); + obs.setValue(new StringType("AAAABBBB")); + + myObservationDao.create(obs, mySrd); + + List found = toList(myObservationDao.search(new SearchParameterMap("value-string", new StringParam("AAAABBBB")).setLoadSynchronous(true))); + assertEquals(1, found.size()); + + found = toList(myObservationDao.search(new SearchParameterMap("value-string", new StringParam("AAAABBBBCCC")).setLoadSynchronous(true))); + assertEquals(0, found.size()); + + } + + @Test + public void testPersistSearchParamQuantity() { + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("testPersistSearchParamQuantity"); + obs.setValue(new Quantity(111)); + + myObservationDao.create(obs, mySrd); + + List found = toList(myObservationDao.search(new SearchParameterMap("value-quantity", new QuantityParam(111)).setLoadSynchronous(true))); + assertEquals(1, found.size()); + + found = toList(myObservationDao.search(new SearchParameterMap("value-quantity", new QuantityParam(112)).setLoadSynchronous(true))); + assertEquals(0, found.size()); + + found = toList(myObservationDao.search(new SearchParameterMap("value-quantity", new QuantityParam(212)).setLoadSynchronous(true))); + assertEquals(0, found.size()); + + } + + @Test + public void testPersistSearchParams() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001testPersistSearchParams"); + patient.getGenderElement().setValue(AdministrativeGender.MALE); + patient.addName().setFamily("Tester").addGiven("JoetestPersistSearchParams"); + + MethodOutcome outcome = myPatientDao.create(patient, mySrd); + assertNotNull(outcome.getId()); + assertFalse(outcome.getId().isEmpty()); + + long id = outcome.getId().getIdPartAsLong(); + + TokenParam value = new TokenParam("urn:system", "001testPersistSearchParams"); + List found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_IDENTIFIER, value).setLoadSynchronous(true)));; + assertEquals(1, found.size()); + assertEquals(id, found.get(0).getIdElement().getIdPartAsLong().longValue()); + + // found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "M")); + // assertEquals(1, found.size()); + // assertEquals(id, found.get(0).getId().asLong().longValue()); + // + // found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "F")); + // assertEquals(0, found.size()); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "001testPersistSearchParams")); + map.add(Patient.SP_GENDER, new TokenParam("urn:some:wrong:system", AdministrativeGender.MALE.toCode())); + found = toList(myPatientDao.search(map)); + assertEquals(0, found.size()); + + // Now with no system on the gender (should match) + map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "001testPersistSearchParams")); + map.add(Patient.SP_GENDER, new TokenParam(null, AdministrativeGender.MALE.toCode())); + found = toList(myPatientDao.search(map)); + assertEquals(1, found.size()); + assertEquals(id, found.get(0).getIdElement().getIdPartAsLong().longValue()); + + // Now with the wrong gender + map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "001testPersistSearchParams")); + map.add(Patient.SP_GENDER, new TokenParam(AdministrativeGender.MALE.getSystem(), AdministrativeGender.FEMALE.toCode())); + found = toList(myPatientDao.search(map)); + assertEquals(0, found.size()); + + } + + @Test + public void testQuestionnaireTitleGetsIndexed() { + Questionnaire q = new Questionnaire(); + q.setTitle("testQuestionnaireTitleGetsIndexedQ_TITLE"); + IIdType qid1 = myQuestionnaireDao.create(q, mySrd).getId().toUnqualifiedVersionless(); + q = new Questionnaire(); + q.setTitle("testQuestionnaireTitleGetsIndexedQ_NOTITLE"); + IIdType qid2 = myQuestionnaireDao.create(q, mySrd).getId().toUnqualifiedVersionless(); + + IBundleProvider results = myQuestionnaireDao.search(new SearchParameterMap("title", new StringParam("testQuestionnaireTitleGetsIndexedQ_TITLE")).setLoadSynchronous(true)); + assertEquals(1, results.size().intValue()); + assertEquals(qid1, results.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + assertNotEquals(qid2, results.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless()); + + } + + @Test + public void testRead() { + Observation o1 = new Observation(); + o1.getCode().addCoding().setSystem("foo").setCode("testRead"); + IIdType id1 = myObservationDao.create(o1, mySrd).getId(); + + /* + * READ + */ + + reset(myInterceptor); + Observation obs = myObservationDao.read(id1.toUnqualifiedVersionless(), mySrd); + assertEquals(o1.getCode().getCoding().get(0).getCode(), obs.getCode().getCoding().get(0).getCode()); + + // Verify interceptor + ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.READ), detailsCapt.capture()); + ActionRequestDetails details = detailsCapt.getValue(); + assertEquals(id1.toUnqualifiedVersionless().getValue(), details.getId().toUnqualifiedVersionless().getValue()); + assertEquals("Observation", details.getResourceType()); + + /* + * VREAD + */ + assertTrue(id1.hasVersionIdPart()); // just to make sure.. + reset(myInterceptor); + obs = myObservationDao.read(id1, mySrd); + assertEquals(o1.getCode().getCoding().get(0).getCode(), obs.getCode().getCoding().get(0).getCode()); + + // Verify interceptor + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.VREAD), detailsCapt.capture()); + details = detailsCapt.getValue(); + assertEquals(id1.toUnqualified().getValue(), details.getId().toUnqualified().getValue()); + assertEquals("Observation", details.getResourceType()); + + } + + @Test + public void testReadForcedIdVersionHistory() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testReadVorcedIdVersionHistory01"); + p1.setId("testReadVorcedIdVersionHistory"); + IIdType p1id = new IdType(myPatientDao.update(p1, mySrd).getId().getValue()); + assertEquals("testReadVorcedIdVersionHistory", p1id.getIdPart()); + + p1.addIdentifier().setSystem("urn:system").setValue("testReadVorcedIdVersionHistory02"); + p1.setId(p1id); + IIdType p1idv2 = myPatientDao.update(p1, mySrd).getId(); + assertEquals("testReadVorcedIdVersionHistory", p1idv2.getIdPart()); + + assertNotEquals(p1id.getValue(), p1idv2.getValue()); + + Patient v1 = myPatientDao.read(p1id, mySrd); + assertEquals(1, v1.getIdentifier().size()); + + Patient v2 = myPatientDao.read(p1idv2, mySrd); + assertEquals(2, v2.getIdentifier().size()); + + } + + @Test + public void testReadInvalidVersion() throws Exception { + String methodName = "testReadInvalidVersion"; + + Patient pat = new Patient(); + pat.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(pat, mySrd).getId(); + + assertEquals(methodName, myPatientDao.read(id, mySrd).getIdentifier().get(0).getValue()); + + try { + myPatientDao.read(id.withVersion("0"), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Version \"0\" is not valid for resource Patient/" + id.getIdPart(), e.getMessage()); + } + + try { + myPatientDao.read(id.withVersion("2"), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Version \"2\" is not valid for resource Patient/" + id.getIdPart(), e.getMessage()); + } + + try { + myPatientDao.read(id.withVersion("H"), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Version \"H\" is not valid for resource Patient/" + id.getIdPart(), e.getMessage()); + } + + try { + myPatientDao.read(new IdType("Patient/9999999999999/_history/1"), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Resource Patient/9999999999999/_history/1 is not known", e.getMessage()); + } + + } + + @Test + public void testReadWithDeletedResource() { + String methodName = "testReadWithDeletedResource"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = myPatientDao.create(patient, mySrd).getId().toVersionless(); + myPatientDao.delete(id, mySrd); + + assertGone(id); + + patient.setId(id); + patient.addAddress().addLine("AAA"); + myPatientDao.update(patient, mySrd); + + Patient p; + + p = myPatientDao.read(id, mySrd); + assertEquals(1, (p).getName().size()); + + p = myPatientDao.read(id.withVersion("1"), mySrd); + assertEquals(1, (p).getName().size()); + + try { + myPatientDao.read(id.withVersion("2"), mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + p = myPatientDao.read(id.withVersion("3"), mySrd); + assertEquals(1, (p).getName().size()); + } + + @Test + public void testRemoveTag() { + + String methodName = "testResourceMetaOperation"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + patient.getMeta().addTag(null, "Dog", "Puppies"); + + patient.getMeta().addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1"); + + patient.getMeta().addProfile(("http://profile/1")); + + id1 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + + patient.getMeta().addTag("http://foo", "Cat", "Kittens"); + patient.getMeta().addSecurity().setSystem("seclabel:sys:2").setCode("seclabel:code:2").setDisplay("seclabel:dis:2"); + patient.getMeta().addProfile("http://profile/2"); + + id2 = myPatientDao.create(patient, mySrd).getId(); + } + { + Device device = new Device(); + device.addIdentifier().setSystem("urn:system").setValue(methodName); + device.getMeta().addTag("http://foo", "Foo", "Bars"); + device.getMeta().addSecurity().setSystem("seclabel:sys:3").setCode("seclabel:code:3").setDisplay("seclabel:dis:3"); + device.getMeta().addProfile("http://profile/3"); + myDeviceDao.create(device, mySrd); + } + + Meta meta; + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + List published = meta.getTag(); + assertEquals(2, published.size()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + List secLabels = meta.getSecurity(); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + List profiles = meta.getProfile(); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + myPatientDao.removeTag(id1, TagTypeEnum.TAG, null, "Dog"); + myPatientDao.removeTag(id1, TagTypeEnum.SECURITY_LABEL, "seclabel:sys:1", "seclabel:code:1"); + myPatientDao.removeTag(id1, TagTypeEnum.PROFILE, BaseHapiFhirDao.NS_JPA_PROFILE, "http://profile/1"); + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + } + + /** + * Can we handle content that was previously saved containing vocabulary that + * is no longer valid + */ + @Test + public void testResourceInDatabaseContainsInvalidVocabulary() { + final Patient p = new Patient(); + p.setGender(AdministrativeGender.MALE); + final IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + TransactionTemplate tx = new TransactionTemplate(myTxManager); + tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + tx.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + String newContent = myFhirCtx.newJsonParser().encodeResourceToString(p); + newContent = newContent.replace("male", "foo"); + table.setResource(newContent.getBytes(Charsets.UTF_8)); + table.setEncoding(ResourceEncodingEnum.JSON); + myResourceTableDao.save(table); + } + }); + + Patient read = myPatientDao.read(id); + String string = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(read); + ourLog.info(string); + assertThat(string, containsString("value=\"foo\"")); + } + + @Test + public void testResourceInstanceMetaOperation() { + + String methodName = "testResourceInstanceMetaOperation"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + id1 = myPatientDao.create(patient, mySrd).getId(); + + Meta metaAdd = new Meta(); + metaAdd.addTag().setSystem((String) null).setCode("Dog").setDisplay("Puppies"); + metaAdd.addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1"); + metaAdd.addProfile("http://profile/1"); + myPatientDao.metaAddOperation(id1, metaAdd, mySrd); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + patient.getMeta().addTag("http://foo", "Cat", "Kittens"); + + patient.getMeta().addSecurity().setSystem("seclabel:sys:2").setCode("seclabel:code:2").setDisplay("seclabel:dis:2"); + + patient.getMeta().addProfile(("http://profile/2")); + + id2 = myPatientDao.create(patient, mySrd).getId(); + } + { + Device device = new Device(); + device.addIdentifier().setSystem("urn:system").setValue(methodName); + device.getMeta().addTag("http://foo", "Foo", "Bars"); + + device.getMeta().addSecurity().setSystem("seclabel:sys:3").setCode("seclabel:code:3").setDisplay("seclabel:dis:3"); + + device.getMeta().addProfile("http://profile/3"); + + myDeviceDao.create(device, mySrd); + } + + Meta meta; + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + List published = meta.getTag(); + assertEquals(2, published.size()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + List secLabels = meta.getSecurity(); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + List profiles = meta.getProfile(); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + { + Meta metaDel = new Meta(); + metaDel.addTag().setSystem((String) null).setCode("Dog"); + metaDel.addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1"); + metaDel.addProfile("http://profile/1"); + myPatientDao.metaDeleteOperation(id1, metaDel, mySrd); + } + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + } + + @Test + public void testResourceMetaOperation() { + + String methodName = "testResourceMetaOperation"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + patient.getMeta().addTag(null, "Dog", "Puppies"); + + patient.getMeta().addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1"); + + patient.getMeta().addProfile(("http://profile/1")); + + id1 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + + patient.getMeta().addTag("http://foo", "Cat", "Kittens"); + patient.getMeta().addSecurity().setSystem("seclabel:sys:2").setCode("seclabel:code:2").setDisplay("seclabel:dis:2"); + patient.getMeta().addProfile("http://profile/2"); + + id2 = myPatientDao.create(patient, mySrd).getId(); + } + { + Device device = new Device(); + device.addIdentifier().setSystem("urn:system").setValue(methodName); + device.getMeta().addTag("http://foo", "Foo", "Bars"); + device.getMeta().addSecurity().setSystem("seclabel:sys:3").setCode("seclabel:code:3").setDisplay("seclabel:dis:3"); + device.getMeta().addProfile("http://profile/3"); + myDeviceDao.create(device, mySrd); + } + + Meta meta; + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + List published = meta.getTag(); + assertEquals(2, published.size()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + List secLabels = meta.getSecurity(); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + List profiles = meta.getProfile(); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + myPatientDao.removeTag(id1, TagTypeEnum.TAG, null, "Dog", mySrd); + myPatientDao.removeTag(id1, TagTypeEnum.SECURITY_LABEL, "seclabel:sys:1", "seclabel:code:1", mySrd); + myPatientDao.removeTag(id1, TagTypeEnum.PROFILE, BaseHapiFhirDao.NS_JPA_PROFILE, "http://profile/1", mySrd); + + meta = myPatientDao.metaGetOperation(Meta.class, mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + } + + @Test + public void testReverseIncludes() { + String methodName = "testReverseIncludes"; + Organization org = new Organization(); + org.setName("X" + methodName + "X"); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId(); + + Patient pat = new Patient(); + pat.addName().setFamily("X" + methodName + "X"); + pat.getManagingOrganization().setReferenceElement(orgId.toUnqualifiedVersionless()); + myPatientDao.create(pat, mySrd); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Organization.SP_NAME, new StringParam("X" + methodName + "X")); + map.setRevIncludes(Collections.singleton(Patient.INCLUDE_ORGANIZATION)); + IBundleProvider resultsP = myOrganizationDao.search(map); + assertEquals(1, resultsP.size().intValue()); + + List results = resultsP.getResources(0, resultsP.size()); + assertEquals(2, results.size()); + assertEquals(Organization.class, results.get(0).getClass()); + assertEquals(BundleEntrySearchModeEnum.MATCH.getCode(), ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get((IAnyResource) results.get(0))); + assertEquals(Patient.class, results.get(1).getClass()); + assertEquals(BundleEntrySearchModeEnum.INCLUDE.getCode(), ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get((IAnyResource) results.get(1))); + } + + @Test() + public void testSortByComposite() { + Observation o = new Observation(); + o.getCode().setText("testSortByComposite"); + myObservationDao.create(o, mySrd); + + SearchParameterMap pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Observation.SP_CODE_VALUE_CONCEPT)); + try { + myObservationDao.search(pm).size(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("This server does not support _sort specifications of type COMPOSITE - Can't serve _sort=code-value-concept", e.getMessage()); + } + } + + @Test + public void testSortByDate() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testtestSortByDate"); + p.addName().setFamily("testSortF1").addGiven("testSortG1"); + p.setBirthDateElement(new DateType("2001-01-01")); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + // Create out of order + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testtestSortByDate"); + p.addName().setFamily("testSortF2").addGiven("testSortG2"); + p.setBirthDateElement(new DateType("2001-01-03")); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testtestSortByDate"); + p.addName().setFamily("testSortF3").addGiven("testSortG3"); + p.setBirthDateElement(new DateType("2001-01-02")); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testtestSortByDate"); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + List actual; + SearchParameterMap pm; + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate")); + pm.setSort(new SortSpec(Patient.SP_BIRTHDATE)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate")); + pm.setSort(new SortSpec(Patient.SP_BIRTHDATE).setOrder(SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate")); + pm.setSort(new SortSpec(Patient.SP_BIRTHDATE).setOrder(SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + // The first would be better, but JPA doesn't do NULLS LAST + // assertThat(actual, contains(id3, id2, id1, id4)); + assertThat(actual, contains(id4, id3, id2, id1)); + + } + + @Test + public void testSortById() { + String methodName = "testSortBTyId"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.setId(methodName); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType idMethodName = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); + assertEquals(methodName, idMethodName.getIdPart()); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(5, actual.size()); + assertThat(actual, contains(idMethodName, id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(5, actual.size()); + assertThat(actual, contains(idMethodName, id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(5, actual.size()); + assertThat(actual, contains(id4, id3, id2, id1, idMethodName)); + } + + @Test + public void testSortByLastUpdated() { + String methodName = "testSortByLastUpdated"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system1").setValue(methodName); + p.addName().setFamily(methodName); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system2").setValue(methodName); + p.addName().setFamily(methodName); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system3").setValue(methodName); + p.addName().setFamily(methodName); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system4").setValue(methodName); + p.addName().setFamily(methodName); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertThat(actual, contains(id4, id3, id2, id1)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam(null, methodName)); + pm.setSort(new SortSpec(Patient.SP_NAME).setChain(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.DESC))); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertThat(actual, contains(id4, id3, id2, id1)); + } + + @Test + public void testSortByNumber() { + String methodName = "testSortByNumber"; + + Encounter e1 = new Encounter(); + e1.addIdentifier().setSystem("foo").setValue(methodName); + e1.getLength().setSystem(BaseHapiFhirDao.UCUM_NS).setCode("min").setValue(4.0 * 24 * 60); + IIdType id1 = myEncounterDao.create(e1, mySrd).getId().toUnqualifiedVersionless(); + + Encounter e3 = new Encounter(); + e3.addIdentifier().setSystem("foo").setValue(methodName); + e3.getLength().setSystem(BaseHapiFhirDao.UCUM_NS).setCode("year").setValue(3.0); + IIdType id3 = myEncounterDao.create(e3, mySrd).getId().toUnqualifiedVersionless(); + + Encounter e2 = new Encounter(); + e2.addIdentifier().setSystem("foo").setValue(methodName); + e2.getLength().setSystem(BaseHapiFhirDao.UCUM_NS).setCode("year").setValue(2.0); + IIdType id2 = myEncounterDao.create(e2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Encounter.SP_LENGTH)); + actual = toUnqualifiedVersionlessIdValues(myEncounterDao.search(pm)); + assertThat(actual, contains(toValues(id1, id2, id3))); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Encounter.SP_LENGTH, SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIdValues(myEncounterDao.search(pm)); + assertThat(actual, contains(toValues(id3, id2, id1))); + } + + public void testSortByQuantity() { + Observation res; + + res = new Observation(); + res.setValue(new Quantity().setSystem("sys1").setCode("code1").setValue(2L)); + IIdType id2 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new Observation(); + res.setValue(new Quantity().setSystem("sys1").setCode("code1").setValue(1L)); + IIdType id1 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new Observation(); + res.setValue(new Quantity().setSystem("sys1").setCode("code1").setValue(3L)); + IIdType id3 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new Observation(); + res.setValue(new Quantity().setSystem("sys1").setCode("code1").setValue(4L)); + IIdType id4 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY)); + List actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY, SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY, SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id4, id3, id2, id1)); + + } + + @Test + public void testSortByReference() { + String methodName = "testSortByReference"; + + Organization o1 = new Organization(); + IIdType oid1 = myOrganizationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Organization o2 = new Organization(); + IIdType oid2 = myOrganizationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("testSortF1").addGiven("testSortG1"); + p.getManagingOrganization().setReferenceElement(oid1); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("testSortF2").addGiven("testSortG2"); + p.getManagingOrganization().setReferenceElement(oid2); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("testSortF3").addGiven("testSortG3"); + p.getManagingOrganization().setReferenceElement(oid1); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.getManagingOrganization().setReferenceElement(oid2); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(Patient.SP_ORGANIZATION)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual.subList(0, 2), containsInAnyOrder(id1, id3)); + assertThat(actual.subList(2, 4), containsInAnyOrder(id2, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(Patient.SP_ORGANIZATION).setOrder(SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual.subList(0, 2), containsInAnyOrder(id1, id3)); + assertThat(actual.subList(2, 4), containsInAnyOrder(id2, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); + pm.setSort(new SortSpec(Patient.SP_ORGANIZATION).setOrder(SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual.subList(0, 2), containsInAnyOrder(id2, id4)); + assertThat(actual.subList(2, 4), containsInAnyOrder(id1, id3)); + } + + @Test + public void testSortByString01() { + Patient p = new Patient(); + String string = "testSortByString01"; + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("testSortF1").addGiven("testSortG1"); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + // Create out of order + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("testSortF3").addGiven("testSortG3"); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("testSortF2").addGiven("testSortG2"); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.ASC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + // The first would be better, but JPA doesn't do NULLS LAST + // assertThat(actual, contains(id3, id2, id1, id4)); + assertThat(actual, contains(id4, id3, id2, id1)); + } + + /** + * See #198 + */ + @Test + public void testSortByString02() { + Patient p; + String string = "testSortByString02"; + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("Fam1").addGiven("Giv1"); + myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("Fam2").addGiven("Giv1"); + myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("Fam2").addGiven("Giv2"); + myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(string); + p.addName().setFamily("Fam1").addGiven("Giv2"); + myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List names; + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY)); + names = extractNames(myPatientDao.search(pm)); + ourLog.info("Names: {}", names); + assertThat(names.subList(0, 2), containsInAnyOrder("Giv1 Fam1", "Giv2 Fam1")); + assertThat(names.subList(2, 4), containsInAnyOrder("Giv1 Fam2", "Giv2 Fam2")); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY).setChain(new SortSpec(Patient.SP_GIVEN))); + names = extractNames(myPatientDao.search(pm)); + ourLog.info("Names: {}", names); + assertThat(names.subList(0, 2), contains("Giv1 Fam1", "Giv2 Fam1")); + assertThat(names.subList(2, 4), contains("Giv1 Fam2", "Giv2 Fam2")); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.DESC))); + names = extractNames(myPatientDao.search(pm)); + ourLog.info("Names: {}", names); + assertThat(names.subList(0, 2), contains("Giv2 Fam1", "Giv1 Fam1")); + assertThat(names.subList(2, 4), contains("Giv2 Fam2", "Giv1 Fam2")); + + pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string)); + pm.setSort(new SortSpec(Patient.SP_FAMILY, SortOrderEnum.DESC).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.DESC))); + names = extractNames(myPatientDao.search(pm)); + ourLog.info("Names: {}", names); + assertThat(names.subList(0, 2), contains("Giv2 Fam2", "Giv1 Fam2")); + assertThat(names.subList(2, 4), contains("Giv2 Fam1", "Giv1 Fam1")); + } + + @Test + public void testSortByToken() { + String methodName = "testSortByToken"; + + Patient p; + + p = new Patient(); + p.addIdentifier().setSystem("urn:system2").setValue(methodName + "1"); + IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system1").setValue(methodName + "2"); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system1").setValue(methodName + "1"); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system2").setValue(methodName + "2"); + IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm; + List actual; + + pm = new SearchParameterMap(); + TokenOrListParam sp = new TokenOrListParam(); + sp.addOr(new TokenParam("urn:system1", methodName + "1")); + sp.addOr(new TokenParam("urn:system1", methodName + "2")); + sp.addOr(new TokenParam("urn:system2", methodName + "1")); + sp.addOr(new TokenParam("urn:system2", methodName + "2")); + pm.add(Patient.SP_IDENTIFIER, sp); + pm.setSort(new SortSpec(Patient.SP_IDENTIFIER)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + sp = new TokenOrListParam(); + sp.addOr(new TokenParam("urn:system1", methodName + "1")); + sp.addOr(new TokenParam("urn:system1", methodName + "2")); + sp.addOr(new TokenParam("urn:system2", methodName + "1")); + sp.addOr(new TokenParam("urn:system2", methodName + "2")); + pm.add(Patient.SP_IDENTIFIER, sp); + pm.setSort(new SortSpec(Patient.SP_IDENTIFIER, SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id4, id3, id2, id1)); + + } + + public void testSortByUri() { + ConceptMap res = new ConceptMap(); + res.addGroup().setSource("http://foo2"); + IIdType id2 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new ConceptMap(); + res.addGroup().setSource("http://foo1"); + IIdType id1 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new ConceptMap(); + res.addGroup().setSource("http://bar3"); + IIdType id3 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + res = new ConceptMap(); + res.addGroup().setSource("http://bar4"); + IIdType id4 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap pm = new SearchParameterMap(); + pm.setSort(new SortSpec(ConceptMap.SP_SOURCE)); + List actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); + + pm = new SearchParameterMap(); + pm.setSort(new SortSpec(Encounter.SP_LENGTH, SortOrderEnum.DESC)); + actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id4, id3, id2, id1)); + + } + + @Test + public void testSortNoMatches() { + String methodName = "testSortNoMatches"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + + map = new SearchParameterMap(); + map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart())); + map.setLastUpdated(new DateRangeParam("2001", "2003")); + map.setSort(new SortSpec(Constants.PARAM_LASTUPDATED)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), empty()); + + map = new SearchParameterMap(); + map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart())); + map.setLastUpdated(new DateRangeParam("2001", "2003")); + map.setSort(new SortSpec(Patient.SP_NAME)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), empty()); + + } + + @Test + public void testStoreUnversionedResources() { + Organization o1 = new Organization(); + o1.getNameElement().setValue("AAA"); + IIdType o1id = myOrganizationDao.create(o1, mySrd).getId(); + assertTrue(o1id.hasVersionIdPart()); + + Patient p1 = new Patient(); + p1.addName().setFamily("AAAA"); + p1.getManagingOrganization().setReferenceElement(o1id); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + + p1 = myPatientDao.read(p1id, mySrd); + + assertFalse(p1.getManagingOrganization().getReferenceElement().hasVersionIdPart()); + assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReferenceElement().toUnqualifiedVersionless()); + } + + /** + * Test for issue #60 + */ + @Test + public void testStoreUtf8Characters() throws Exception { + Organization org = new Organization(); + org.setName("測試醫院"); + org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01"); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId(); + + Organization returned = myOrganizationDao.read(orgId, mySrd); + String val = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned); + + ourLog.info(val); + assertThat(val, containsString("")); + } + + @Test + public void testStringParamWhichIsTooLong() { + + Organization org = new Organization(); + String str = "testStringParamLong__lvdaoy843s89tll8gvs89l4s3gelrukveilufyebrew8r87bv4b77feli7fsl4lv3vb7rexloxe7olb48vov4o78ls7bvo7vb48o48l4bb7vbvx"; + str = str + str; + org.getNameElement().setValue(str); + + assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); + + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P"))); + int initial = val.size(); + + myOrganizationDao.create(org, mySrd); + + val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P"))); + assertEquals(initial + 0, val.size()); + + val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH)))); + assertEquals(initial + 1, val.size()); + + try { + myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH + 1)))); + fail(); + } catch (InvalidRequestException e) { + // ok + } + } + + @Test + public void testTagsAndProfilesAndSecurityLabelsWithCreateAndReadAndSearch() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testTagsWithCreateAndReadAndSearch"); + patient.addName().setFamily("Tester").addGiven("Joe"); + List tagList = new ArrayList(); + tagList.add(new Coding().setSystem(null).setCode("Dog").setDisplay("Puppies")); + // Add this twice + tagList.add(new Coding().setSystem("http://foo").setCode("Cat").setDisplay("Kittens")); + tagList.add(new Coding().setSystem("http://foo").setCode("Cat").setDisplay("Kittens")); + patient.getMeta().getTag().addAll(tagList); + + List securityLabels = new ArrayList(); + securityLabels.add(new Coding().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1")); + securityLabels.add(new Coding().setSystem("seclabel:sys:2").setCode("seclabel:code:2").setDisplay("seclabel:dis:2")); + patient.getMeta().getSecurity().addAll(securityLabels); + + List profiles = new ArrayList(); + profiles.add(new IdType("http://profile/1")); + profiles.add(new IdType("http://profile/2")); + patient.getMeta().getProfile().addAll(profiles); + + MethodOutcome outcome = myPatientDao.create(patient, mySrd); + IIdType patientId = outcome.getId(); + assertNotNull(patientId); + assertFalse(patientId.isEmpty()); + + Patient retrieved = myPatientDao.read(patientId, mySrd); + ArrayList published = (ArrayList) retrieved.getMeta().getTag(); + sort(published); + assertEquals(2, published.size()); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + + List secLabels = retrieved.getMeta().getSecurity(); + sortCodings(secLabels); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + profiles = retrieved.getMeta().getProfile(); + profiles = sortIds(profiles); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + List search = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_IDENTIFIER, new TokenParam(patient.getIdentifier().get(0).getSystem(), patient.getIdentifier().get(0).getValue())).setLoadSynchronous(true))); + assertEquals(1, search.size()); + retrieved = search.get(0); + + published = (ArrayList) retrieved.getMeta().getTag(); + sort(published); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + + secLabels = retrieved.getMeta().getSecurity(); + sortCodings(secLabels); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + + profiles = retrieved.getMeta().getProfile(); + profiles = sortIds(profiles); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + myPatientDao.addTag(patientId, TagTypeEnum.TAG, "http://foo", "Cat", "Kittens"); + myPatientDao.addTag(patientId, TagTypeEnum.TAG, "http://foo", "Cow", "Calves"); + + retrieved = myPatientDao.read(patientId, mySrd); + published = (ArrayList) retrieved.getMeta().getTag(); + sort(published); + assertEquals(3, published.size()); + assertEquals(published.toString(), "Dog", published.get(0).getCode()); + assertEquals(published.toString(), "Puppies", published.get(0).getDisplay()); + assertEquals(published.toString(), null, published.get(0).getSystem()); + assertEquals(published.toString(), "Cat", published.get(1).getCode()); + assertEquals(published.toString(), "Kittens", published.get(1).getDisplay()); + assertEquals(published.toString(), "http://foo", published.get(1).getSystem()); + assertEquals(published.toString(), "Cow", published.get(2).getCode()); + assertEquals(published.toString(), "Calves", published.get(2).getDisplay()); + assertEquals(published.toString(), "http://foo", published.get(2).getSystem()); + + secLabels = retrieved.getMeta().getSecurity(); + sortCodings(secLabels); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + + profiles = retrieved.getMeta().getProfile(); + profiles = sortIds(profiles); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + } + + @Test + public void testTimingSearchParams() throws Exception { + Date before = new DateTimeType("2011-01-01T10:00:00Z").getValue(); + Date middle = new DateTimeType("2011-01-02T10:00:00Z").getValue(); + Date after = new DateTimeType("2011-01-03T10:00:00Z").getValue(); + + CarePlan cp = new CarePlan(); + cp.addActivity().getDetail().setScheduled(new Timing().addEvent(before).addEvent(middle).addEvent(after)); + cp.addActivity().getDetail(); + IIdType id = myCarePlanDao.create(cp, mySrd).getId().toUnqualifiedVersionless(); + + CarePlan cp2 = new CarePlan(); + cp2.addActivity().getDetail().setScheduled(new StringType("FOO")); + cp2.addActivity().getDetail(); + myCarePlanDao.create(cp2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2010-01-01T10:00:00Z", null)); + assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), org.hamcrest.Matchers.contains(id.getValue())); + + params = new SearchParameterMap(); + params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2011-01-01T10:00:00Z", null)); + assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), org.hamcrest.Matchers.contains(id.getValue())); + + params = new SearchParameterMap(); + params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2012-01-01T10:00:00Z", null)); + assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), org.hamcrest.Matchers.empty()); + } + + @Test + public void testTokenParamWhichIsTooLong() { + + String longStr1 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); + String longStr2 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); + + Organization org = new Organization(); + org.getNameElement().setValue("testTokenParamWhichIsTooLong"); + org.addType().addCoding().setSystem(longStr1).setCode(longStr2); + + String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2))); + int initial = val.size(); + + myOrganizationDao.create(org, mySrd); + + val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2))); + assertEquals(initial + 1, val.size()); + + try { + myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(longStr1, subStr2))); + fail(); + } catch (InvalidRequestException e) { + // ok + } + + try { + myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, longStr2))); + fail(); + } catch (InvalidRequestException e) { + // ok + } + } + + @Test + public void testUpdateRejectsIdWhichPointsToForcedId() throws InterruptedException { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsIdWhichPointsToForcedId01"); + p1.addName().setFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId01"); + p1.setId("ABABA"); + IIdType p1id = myPatientDao.update(p1, mySrd).getId(); + assertEquals("ABABA", p1id.getIdPart()); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsIdWhichPointsToForcedId02"); + p2.addName().setFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId02"); + IIdType p2id = myPatientDao.create(p2, mySrd).getId(); + long p1longId = p2id.getIdPartAsLong() - 1; + + try { + myPatientDao.read(new IdType("Patient/" + p1longId), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + + try { + p1.setId(new IdType("Patient/" + p1longId)); + myPatientDao.update(p1, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("clients may only assign IDs which contain at least one non-numeric")); + } + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + public static void assertConflictException(ResourceVersionConflictException e) { + assertThat(e.getMessage(), matchesPattern( + "Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource [a-zA-Z]+/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+")); + } + + private static List toStringList(List theUriType) { + ArrayList retVal = new ArrayList(); + for (UriType next : theUriType) { + retVal.add(next.getValue()); + } + return retVal; + } + +} 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 new file mode 100644 index 00000000000..6e140f693fb --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -0,0 +1,868 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import java.util.*; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.mockito.ArgumentCaptor; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UpdateTest.class); + + @Test + public void testCreateAndUpdateWithoutRequest() throws Exception { + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + IIdType id = myPatientDao.create(p).getId().toUnqualified(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + p.setActive(true); + IIdType id2 = myPatientDao.create(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); + assertEquals(id.getValue(), id2.getValue()); + + p = new Patient(); + p.setId(id); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + p.setActive(false); + myPatientDao.update(p).getId(); + + p.setActive(true); + id2 = myPatientDao.update(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); + assertEquals(id.getIdPart(), id2.getIdPart()); + assertEquals("3", id2.getVersionIdPart()); + + Patient newPatient = myPatientDao.read(id); + assertEquals("1", newPatient.getIdElement().getVersionIdPart()); + + newPatient = myPatientDao.read(id.toVersionless()); + assertEquals("3", newPatient.getIdElement().getVersionIdPart()); + + myPatientDao.delete(id.toVersionless()); + + try { + myPatientDao.read(id.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // nothing + } + + } + + @Test + public void testDuplicateProfilesIgnored() { + String name = "testDuplicateProfilesIgnored"; + IIdType id; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + + List tl = new ArrayList(); + tl.add(new IdType("http://foo/bar")); + tl.add(new IdType("http://foo/bar")); + tl.add(new IdType("http://foo/bar")); + patient.getMeta().getProfile().addAll(tl); + + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getProfile(); + assertEquals(1, tl.size()); + assertEquals("http://foo/bar", tl.get(0).getValue()); + } + + } + + @After + public void afterResetDao() { + myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); + } + + @Test + public void testHardMetaCapIsEnforcedOnCreate() { + myDaoConfig.setResourceMetaCountHardLimit(3); + + IIdType id; + { + Patient patient = new Patient(); + patient.getMeta().addTag().setSystem("http://foo").setCode("1"); + patient.getMeta().addTag().setSystem("http://foo").setCode("2"); + patient.getMeta().addTag().setSystem("http://foo").setCode("3"); + patient.getMeta().addTag().setSystem("http://foo").setCode("4"); + patient.setActive(true); + try { + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource contains 4 meta entries (tag/profile/security label), maximum is 3", e.getMessage()); + } + } + } + + @Test + public void testHardMetaCapIsEnforcedOnMetaAdd() { + myDaoConfig.setResourceMetaCountHardLimit(3); + + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("1"); + meta.addTag().setSystem("http://foo").setCode("2"); + meta.addTag().setSystem("http://foo").setCode("3"); + meta.addTag().setSystem("http://foo").setCode("4"); + try { + myPatientDao.metaAddOperation(id, meta, null); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource contains 4 meta entries (tag/profile/security label), maximum is 3", e.getMessage()); + } + + } + } + + @Test + public void testDuplicateTagsOnAddTagsIgnored() { + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.metaAddOperation(id, meta, null); + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + } + + @Test + public void testDuplicateTagsOnUpdateIgnored() { + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + Patient patient = new Patient(); + patient.setId(id); + patient.setActive(true); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.update(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read on second version + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + // Do a read on first version + { + Patient patient = myPatientDao.read(id.withVersion("1"), mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(0, tl.size()); + } + + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.metaAddOperation(id.withVersion("1"), meta, null); + + // Do a read on first version + { + Patient patient = myPatientDao.read(id.withVersion("1"), mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + } + + @Test + public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForDiscreteUpdates() { + + // First time + Patient p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + String id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + // Second time should not result in an update + p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + // And third time should not result in an update + p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + myPatientDao.read(new IdType("Patient/A")); + myPatientDao.read(new IdType("Patient/A/_history/1")); + try { + myPatientDao.read(new IdType("Patient/A/_history/2")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + try { + myPatientDao.read(new IdType("Patient/A/_history/3")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + + // Create one more + p = new Patient(); + p.setActive(false); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/2")); + + } + + @Test + public void testUpdateAndGetHistoryResource() throws InterruptedException { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + + MethodOutcome outcome = myPatientDao.create(patient, mySrd); + assertNotNull(outcome.getId()); + assertFalse(outcome.getId().isEmpty()); + + assertEquals("1", outcome.getId().getVersionIdPart()); + + Date now = new Date(); + Patient retrieved = myPatientDao.read(outcome.getId(), mySrd); + InstantType updated = retrieved.getMeta().getLastUpdatedElement().copy(); + assertTrue(updated.before(now)); + + Thread.sleep(1000); + + reset(myInterceptor); + retrieved.getIdentifier().get(0).setValue("002"); + MethodOutcome outcome2 = myPatientDao.update(retrieved, mySrd); + assertEquals(outcome.getId().getIdPart(), outcome2.getId().getIdPart()); + assertNotEquals(outcome.getId().getVersionIdPart(), outcome2.getId().getVersionIdPart()); + assertEquals("2", outcome2.getId().getVersionIdPart()); + + // Verify interceptor + ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.UPDATE), detailsCapt.capture()); + ActionRequestDetails details = detailsCapt.getValue(); + assertNotNull(details.getId()); + assertEquals("Patient", details.getResourceType()); + assertEquals(Patient.class, details.getResource().getClass()); + + Date now2 = new Date(); + + Patient retrieved2 = myPatientDao.read(outcome.getId().toVersionless(), mySrd); + + assertEquals("2", retrieved2.getIdElement().getVersionIdPart()); + assertEquals("002", retrieved2.getIdentifier().get(0).getValue()); + InstantType updated2 = retrieved2.getMeta().getLastUpdatedElement(); + assertTrue(updated2.after(now)); + assertTrue(updated2.before(now2)); + + Thread.sleep(2000); + + /* + * Get history + */ + + IBundleProvider historyBundle = myPatientDao.history(outcome.getId(), null, null, mySrd); + + assertEquals(2, historyBundle.size().intValue()); + + List history = historyBundle.getResources(0, 2); + + ourLog.info("updated : {}", updated.getValueAsString()); + ourLog.info(" * Exp : {}", ((Resource) history.get(1)).getMeta().getLastUpdatedElement().getValueAsString()); + ourLog.info("updated2: {}", updated2.getValueAsString()); + ourLog.info(" * Exp : {}", ((Resource) history.get(0)).getMeta().getLastUpdatedElement().getValueAsString()); + + assertEquals("1", history.get(1).getIdElement().getVersionIdPart()); + assertEquals("2", history.get(0).getIdElement().getVersionIdPart()); + assertEquals(updated.getValueAsString(), ((Resource) history.get(1)).getMeta().getLastUpdatedElement().getValueAsString()); + assertEquals("001", ((Patient) history.get(1)).getIdentifier().get(0).getValue()); + assertEquals(updated2.getValueAsString(), ((Resource) history.get(0)).getMeta().getLastUpdatedElement().getValueAsString()); + assertEquals("002", ((Patient) history.get(0)).getIdentifier().get(0).getValue()); + + } + + @Test + public void testUpdateByUrl() { + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + + myPatientDao.update(p, "Patient?identifier=urn%3Asystem%7C" + methodName, mySrd); + + p = myPatientDao.read(id.toVersionless(), mySrd); + assertThat(p.getIdElement().toVersionless().toString(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); + assertNotEquals(id, p.getIdElement()); + assertThat(p.getIdElement().toString(), endsWith("/_history/2")); + + } + + @Test + public void testUpdateConditionalByLastUpdated() throws Exception { + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + myPatientDao.create(p, mySrd).getId(); + + InstantDt start = InstantDt.withCurrentTime(); + ourLog.info("First time: {}", start.getValueAsString()); + Thread.sleep(100); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got ID: {}", id); + + Thread.sleep(100); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + + String matchUrl = "Patient?_lastUpdated=gt" + start.getValueAsString(); + ourLog.info("URL is: {}", matchUrl); + myPatientDao.update(p, matchUrl, mySrd); + + p = myPatientDao.read(id.toVersionless(), mySrd); + assertThat(p.getIdElement().toVersionless().toString(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); + assertNotEquals(id, p.getIdElement()); + assertThat(p.getIdElement().toString(), endsWith("/_history/2")); + + } + + @Test + public void testUpdateConditionalByLastUpdatedWithWrongTimezone() throws Exception { + TimeZone def = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("GMT-0:00")); + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + myPatientDao.create(p, mySrd).getId(); + + InstantDt start = InstantDt.withCurrentTime(); + Thread.sleep(100); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Thread.sleep(100); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + + myPatientDao.update(p, "Patient?_lastUpdated=gt" + start.getValueAsString(), mySrd); + + p = myPatientDao.read(id.toVersionless(), mySrd); + assertThat(p.getIdElement().toVersionless().toString(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); + assertNotEquals(id, p.getIdElement()); + assertThat(p.getIdElement().toString(), endsWith("/_history/2")); + } finally { + TimeZone.setDefault(def); + } + } + + @Test + public void testUpdateCreatesTextualIdIfItDoesntAlreadyExist() { + Patient p = new Patient(); + String methodName = "testUpdateCreatesTextualIdIfItDoesntAlreadyExist"; + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + + IIdType id = myPatientDao.update(p, mySrd).getId(); + assertEquals("Patient/" + methodName, id.toUnqualifiedVersionless().getValue()); + + p = myPatientDao.read(id, mySrd); + assertEquals(methodName, p.getIdentifier().get(0).getValue()); + } + + @Test + public void testUpdateDoesntFailForUnknownIdWithNumberThenText() { + String methodName = "testUpdateFailsForUnknownIdWithNumberThenText"; + Patient p = new Patient(); + p.setId("0" + methodName); + p.addName().setFamily(methodName); + + myPatientDao.update(p, mySrd); + } + + @Test + @Ignore + public void testUpdateIgnoresIdenticalVersions() { + String methodName = "testUpdateIgnoresIdenticalVersions"; + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + p1.addName().setFamily("Tester").addGiven(methodName); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + + IIdType p1id2 = myPatientDao.update(p1, mySrd).getId(); + assertEquals(p1id.getValue(), p1id2.getValue()); + + p1.addName().addGiven("NewGiven"); + IIdType p1id3 = myPatientDao.update(p1, mySrd).getId(); + assertNotEquals(p1id.getValue(), p1id3.getValue()); + + } + + @Test + public void testUpdateMaintainsSearchParams() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2AAA"); + p1.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2AAA"); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2BBB"); + p2.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB"); + myPatientDao.create(p2, mySrd).getId(); + + Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA"))); + assertEquals(1, ids.size()); + assertThat(ids, contains(p1id.getIdPartAsLong())); + + // Update the name + p1.getName().get(0).getGiven().get(0).setValue("testUpdateMaintainsSearchParamsDstu2BBB"); + MethodOutcome update2 = myPatientDao.update(p1, mySrd); + IIdType p1id2 = update2.getId(); + + ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA"))); + assertEquals(0, ids.size()); + + ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2BBB"))); + assertEquals(2, ids.size()); + + // Make sure vreads work + p1 = myPatientDao.read(p1id, mySrd); + assertEquals("testUpdateMaintainsSearchParamsDstu2AAA", p1.getName().get(0).getGivenAsSingleString()); + + p1 = myPatientDao.read(p1id2, mySrd); + assertEquals("testUpdateMaintainsSearchParamsDstu2BBB", p1.getName().get(0).getGivenAsSingleString()); + + } + + /** + * Per the spec, update should preserve tags and security labels but not profiles + */ + @Test + public void testUpdateMaintainsTagsAndSecurityLabels() { + String methodName = "testUpdateMaintainsTagsAndSecurityLabels"; + + IIdType p1id; + { + Patient p1 = new Patient(); + p1.addName().setFamily(methodName); + + p1.getMeta().addTag("tag_scheme1", "tag_term1", null); + p1.getMeta().addSecurity("sec_scheme1", "sec_term1", null); + p1.getMeta().addProfile("http://foo1"); + + p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient p1 = new Patient(); + p1.setId(p1id); + p1.addName().setFamily(methodName); + + p1.getMeta().addTag("tag_scheme2", "tag_term2", null); + p1.getMeta().addSecurity("sec_scheme2", "sec_term2", null); + p1.getMeta().addProfile("http://foo2"); + + myPatientDao.update(p1, mySrd); + } + { + Patient p1 = myPatientDao.read(p1id, mySrd); + List tagList = p1.getMeta().getTag(); + Set secListValues = new HashSet(); + for (Coding next : tagList) { + secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); + } + assertThat(secListValues, containsInAnyOrder("tag_scheme1|tag_term1", "tag_scheme2|tag_term2")); + List secList = p1.getMeta().getSecurity(); + secListValues = new HashSet(); + for (Coding next : secList) { + secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); + } + assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2")); + List profileList = p1.getMeta().getProfile(); + assertEquals(1, profileList.size()); + assertEquals("http://foo2", profileList.get(0).getValueAsString()); // no foo1 + } + } + + @Test + public void testUpdateModifiesProfiles() { + String name = "testUpdateModifiesProfiles"; + IIdType id; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + + List tl = new ArrayList(); + tl.add(new IdType("http://foo/bar")); + patient.getMeta().getProfile().addAll(tl); + + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getProfile(); + assertEquals(1, tl.size()); + assertEquals("http://foo/bar", tl.get(0).getValue()); + } + + // Update + { + Patient patient = new Patient(); + patient.setId(id); + patient.addName().setFamily(name); + + List tl = new ArrayList(); + tl.add(new IdType("http://foo/baz")); + patient.getMeta().getProfile().clear(); + patient.getMeta().getProfile().addAll(tl); + + id = myPatientDao.update(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getProfile(); + assertEquals(1, tl.size()); + assertEquals("http://foo/baz", tl.get(0).getValue()); + } + + } + + @Test + public void testUpdateRejectsInvalidTypes() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes"); + p1.addName().setFamily("Tester").addGiven("testUpdateRejectsInvalidTypes"); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + + Organization p2 = new Organization(); + p2.getNameElement().setValue("testUpdateRejectsInvalidTypes"); + try { + p2.setId(new IdType("Organization/" + p1id.getIdPart())); + myOrganizationDao.update(p2, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + // good + } + + try { + p2.setId(new IdType("Patient/" + p1id.getIdPart())); + myOrganizationDao.update(p2, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + ourLog.error("Good", e); + } + + } + + @Test + public void testUpdateUnknownNumericIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/9999999999999999"); + try { + myPatientDao.update(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Can not create resource with ID[9999999999999999], no resource with this ID exists and clients may only")); + } + } + + @Test + public void testUpdateWithInvalidIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/123:456"); + try { + myPatientDao.update(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Can not process entity with ID[123:456], this is not a valid FHIR ID", e.getMessage()); + } + } + + @Test + public void testUpdateWithNoChangeDetectionDisabledUpdateUnchanged() { + myDaoConfig.setSuppressUpdatesWithNoChange(false); + + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + // Update + { + Patient patient = new Patient(); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertNotEquals(id1.getValue(), id2.getValue()); + } + + @Test + public void testUpdateWithNoChangeDetectionUpdateTagAdded() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + // Update + { + Patient patient = new Patient(); + patient.getMeta().addTag().setCode("CODE"); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertNotEquals(id1.getValue(), id2.getValue()); + } + + @Test + public void testUpdateWithNoChangeDetectionUpdateTagMetaRemoved() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.getMeta().addTag().setCode("CODE"); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + Meta meta = new Meta(); + meta.addTag().setCode("CODE"); + myPatientDao.metaDeleteOperation(id1, meta, null); + + meta = myPatientDao.metaGetOperation(Meta.class, id1, null); + assertEquals(0, meta.getTag().size()); + + // Update + { + Patient patient = new Patient(); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertEquals(id1.getValue(), id2.getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, null); + assertEquals(0, meta.getTag().size()); + } + + @Test + public void testUpdateWithNoChangeDetectionUpdateTagNoChange() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + // Add tag + Meta meta = new Meta(); + meta.addTag().setCode("CODE"); + myPatientDao.metaAddOperation(id1, meta, null); + + // Update with tag + { + Patient patient = new Patient(); + patient.getMeta().addTag().setCode("CODE"); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertEquals(id1.getValue(), id2.getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, null); + assertEquals(1, meta.getTag().size()); + assertEquals("CODE", meta.getTag().get(0).getCode()); + } + + @Test + public void testUpdateWithNoChangeDetectionUpdateTagRemoved() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.getMeta().addTag().setCode("CODE"); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + // Update + { + Patient patient = new Patient(); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertEquals(id1.getValue(), id2.getValue()); + + Meta meta = myPatientDao.metaGetOperation(Meta.class, id2, null); + assertEquals(1, meta.getTag().size()); + assertEquals("CODE", meta.getTag().get(0).getCode()); + } + + @Test + public void testUpdateWithNoChangeDetectionUpdateUnchanged() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + // Update + { + Patient patient = new Patient(); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertEquals(id1.getValue(), id2.getValue()); + } + + @Test + public void testUpdateWithNumericIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/123"); + try { + myPatientDao.update(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("clients may only assign IDs which contain at least one non-numeric")); + } + } + + @Test + public void testUpdateWithNumericThenTextIdSucceeds() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/123abc"); + IIdType id = myPatientDao.update(p, mySrd).getId(); + assertEquals("123abc", id.getIdPart()); + assertEquals("1", id.getVersionIdPart()); + + p = myPatientDao.read(id.toUnqualifiedVersionless(), mySrd); + assertEquals("Patient/123abc", p.getIdElement().toUnqualifiedVersionless().getValue()); + assertEquals("Hello", p.getName().get(0).getFamily()); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java new file mode 100644 index 00000000000..db983595a99 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -0,0 +1,303 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @Test + public void testValidateStructureDefinition() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/sd-david-dhtest7.json"), StandardCharsets.UTF_8); + StructureDefinition sd = myFhirCtx.newJsonParser().parseResource(StructureDefinition.class, input); + + + ourLog.info("Starting validation"); + try { + myStructureDefinitionDao.validate(sd, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + } catch (PreconditionFailedException e) { + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + } + ourLog.info("Done validation"); + + StopWatch sw = new StopWatch(); + ourLog.info("Starting validation"); + try { + myStructureDefinitionDao.validate(sd, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + } catch (PreconditionFailedException e) { + // ok + } + ourLog.info("Done validation in {}ms", sw.getMillis()); + + } + + @Test + public void testValidateDocument() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/document-bundle-r4.json"), StandardCharsets.UTF_8); + Bundle document = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + + + ourLog.info("Starting validation"); + try { + MethodOutcome outcome = myBundleDao.validate(document, null, null, null, ValidationModeEnum.CREATE, null, mySrd); + } catch (PreconditionFailedException e) { + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + } + ourLog.info("Done validation"); + +// ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome())); + } + + @Test + @Ignore + public void testValidateResourceContainingProfileDeclarationJson() throws Exception { + String methodName = "testValidateResourceContainingProfileDeclarationJson"; + OperationOutcome outcome = doTestValidateResourceContainingProfileDeclaration(methodName, EncodingEnum.JSON); + + String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(ooString); + assertThat(ooString, containsString("Element '.subject': minimum required = 1, but only found 0")); + assertThat(ooString, containsString("Element encounter @ : max allowed = 0, but found 1")); + assertThat(ooString, containsString("Element '.device': minimum required = 1, but only found 0")); + } + + @Test + @Ignore + public void testValidateResourceContainingProfileDeclarationXml() throws Exception { + String methodName = "testValidateResourceContainingProfileDeclarationXml"; + OperationOutcome outcome = doTestValidateResourceContainingProfileDeclaration(methodName, EncodingEnum.XML); + + String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(ooString); + assertThat(ooString, containsString("Element '/f:Observation.subject': minimum required = 1, but only found 0")); + assertThat(ooString, containsString("Element encounter @ /f:Observation: max allowed = 0, but found 1")); + assertThat(ooString, containsString("Element '/f:Observation.device': minimum required = 1, but only found 0")); + } + + private OperationOutcome doTestValidateResourceContainingProfileDeclaration(String methodName, EncodingEnum enc) throws IOException { + Bundle vss = loadResourceFromClasspath(Bundle.class, "/org/hl7/fhir/r4/model/valueset/valuesets.xml"); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-status"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-category"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-codes"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-methods"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-valueabsentreason"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-interpretation"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "body-site"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "referencerange-meaning"), mySrd); + myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-relationshiptypes"), mySrd); + + StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/org/hl7/fhir/r4/model/profile/devicemetricobservation.profile.xml"); + sd.setId(new IdType()); + sd.setUrl("http://example.com/foo/bar/" + methodName); + myStructureDefinitionDao.create(sd, mySrd); + + Observation input = new Observation(); + input.getMeta().getProfile().add(new IdType(sd.getUrl())); + + input.addIdentifier().setSystem("http://acme").setValue("12345"); + input.getContext().setReference("http://foo.com/Encounter/9"); + input.setStatus(ObservationStatus.FINAL); + input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); + + String encoded = null; + MethodOutcome outcome = null; + ValidationModeEnum mode = ValidationModeEnum.CREATE; + switch (enc) { + case JSON: + encoded = myFhirCtx.newJsonParser().encodeResourceToString(input); + try { + myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + fail(); + } catch (PreconditionFailedException e) { + return (OperationOutcome) e.getOperationOutcome(); + } + case XML: + encoded = myFhirCtx.newXmlParser().encodeResourceToString(input); + try { + myObservationDao.validate(input, null, encoded, EncodingEnum.XML, mode, null, mySrd); + fail(); + } catch (PreconditionFailedException e) { + return (OperationOutcome) e.getOperationOutcome(); + } + } + + throw new IllegalStateException(); // shouldn't get here + } + + @Test + public void testValidateResourceContainingProfileDeclarationInvalid() throws Exception { + String methodName = "testValidateResourceContainingProfileDeclarationInvalid"; + + Observation input = new Observation(); + String profileUri = "http://example.com/" + methodName; + input.getMeta().getProfile().add(new IdType(profileUri)); + + input.addIdentifier().setSystem("http://acme").setValue("12345"); + input.getContext().setReference("http://foo.com/Encounter/9"); + input.setStatus(ObservationStatus.FINAL); + input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); + + ValidationModeEnum mode = ValidationModeEnum.CREATE; + String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input); + MethodOutcome outcome = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + + String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); + ourLog.info(ooString); + assertThat(ooString, containsString("StructureDefinition reference \\\"" + profileUri + "\\\" could not be resolved")); + + } + + @Test + public void testValidateForCreate() { + String methodName = "testValidateForCreate"; + + Patient pat = new Patient(); + pat.setId("Patient/123"); + pat.addName().setFamily(methodName); + + try { + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.CREATE, null, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("ID must not be populated")); + } + + pat.setId(""); + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.CREATE, null, mySrd); + + } + + @Test + public void testValidateForUpdate() { + String methodName = "testValidateForUpdate"; + + Patient pat = new Patient(); + pat.setId("Patient/123"); + pat.addName().setFamily(methodName); + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + + pat.setId(""); + + try { + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("ID must be populated")); + } + + } + + @Test + public void testValidateForUpdateWithContained() { + String methodName = "testValidateForUpdate"; + + Organization org = new Organization(); + org.setId("#123"); + + Patient pat = new Patient(); + pat.setId("Patient/123"); + pat.addName().setFamily(methodName); + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + + pat.setId(""); + + try { + myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("ID must be populated")); + } + + } + + @Test + public void testValidateForDelete() { + String methodName = "testValidateForDelete"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat = new Patient(); + pat.addName().setFamily(methodName); + pat.getManagingOrganization().setReference(orgId.getValue()); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + OperationOutcome outcome = null; + try { + myOrganizationDao.validate(null, orgId, null, null, ValidationModeEnum.DELETE, null, mySrd); + fail(); + } catch (ResourceVersionConflictException e) { + outcome = (OperationOutcome) e.getOperationOutcome(); + } + + String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(ooString); + assertThat(ooString, containsString("Unable to delete Organization")); + + pat.setId(patId); + pat.getManagingOrganization().setReference(""); + myPatientDao.update(pat, mySrd); + + outcome = (OperationOutcome) myOrganizationDao.validate(null, orgId, null, null, ValidationModeEnum.DELETE, null, mySrd).getOperationOutcome(); + ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(ooString); + assertThat(ooString, containsString("Ok to delete")); + + } + + private IBaseResource findResourceByIdInBundle(Bundle vss, String name) { + IBaseResource retVal = null; + for (BundleEntryComponent next : vss.getEntry()) { + if (next.getResource().getIdElement().getIdPart().equals(name)) { + retVal = next.getResource(); + break; + } + } + if (retVal == null) { + fail("Can't find VS: " + name); + } + return retVal; + } + + /** + * Format has changed, this is out of date + */ + @Test + @Ignore + public void testValidateNewQuestionnaireFormat() throws Exception { + String input =IOUtils.toString(FhirResourceDaoR4ValidateTest.class.getResourceAsStream("/questionnaire_r4.xml")); + try { + MethodOutcome results = myQuestionnaireDao.validate(null, null, input, EncodingEnum.XML, ValidationModeEnum.UPDATE, null, mySrd); + OperationOutcome oo = (OperationOutcome) results.getOperationOutcome(); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + } catch (PreconditionFailedException e) { + // this is a failure of the test + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + throw e; + } + } + +} 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 new file mode 100644 index 00000000000..9143c030aaa --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -0,0 +1,242 @@ +package ca.uhn.fhir.jpa.dao.r4; + +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 org.hl7.fhir.r4.model.CodeSystem; +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.IdType; +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.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.util.TestUtil; + +public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValueSetTest.class); + + private IIdType myExtensionalVsId; + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Before + @Transactional + public void before02() throws IOException { + ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); + + CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless(); + + } + + @Test + public void testValidateCodeOperationByCodeAndSystemBad() { + UriType valueSetIdentifier = null; + IdType id = null; + CodeType code = new CodeType("8450-9-XXX"); + UriType system = new UriType("http://acme.org"); + StringType display = null; + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertFalse(result.isResult()); + } + + @Test + public void testValidateCodeOperationByCodeAndSystemGood() { + UriType valueSetIdentifier = null; + IdType id = null; + CodeType code = new CodeType("8450-9"); + UriType system = new UriType("http://acme.org"); + StringType display = null; + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertTrue(result.isResult()); + assertEquals("Systolic blood pressure--expiration", result.getDisplay()); + } + + @Test + public void testValidateCodeOperationByIdentifierAndCodeAndSystem() { + UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); + IdType id = null; + CodeType code = new CodeType("11378-7"); + UriType system = new UriType("http://acme.org"); + StringType display = null; + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertTrue(result.isResult()); + assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + } + + @Test + public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() { + UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); + IdType id = null; + CodeType code = new CodeType("11378-7"); + UriType system = new UriType("http://acme.org"); + StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertFalse(result.isResult()); + assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + } + + @Test + public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndGoodDisplay() { + UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); + IdType id = null; + CodeType code = new CodeType("11378-7"); + UriType system = new UriType("http://acme.org"); + StringType display = new StringType("Systolic blood pressure at First encounter"); + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertTrue(result.isResult()); + assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + } + + @Test + public void testValidateCodeOperationByResourceIdAndCodeableConcept() { + UriType valueSetIdentifier = null; + IIdType id = myExtensionalVsId; + CodeType code = null; + UriType system = null; + StringType display = null; + Coding coding = null; + CodeableConcept codeableConcept = new CodeableConcept(); + codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertTrue(result.isResult()); + assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + } + + @Test + public void testValidateCodeOperationByResourceIdAndCodeAndSystem() { + UriType valueSetIdentifier = null; + IIdType id = myExtensionalVsId; + CodeType code = new CodeType("11378-7"); + UriType system = new UriType("http://acme.org"); + StringType display = null; + Coding coding = null; + CodeableConcept codeableConcept = null; + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + assertTrue(result.isResult()); + assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + } + + @Test + public void testExpandById() throws IOException { + String resp; + + ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + /* + * Filter with display name + */ + + expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic"), mySrd); + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + + } + + @Test + @Ignore + public void testExpandByIdentifier() { + ValueSet expanded = myValueSetDao.expandByIdentifier("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", "11378"); + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + + assertThat(resp, not(containsString(""))); + } + + /** + * This type of expansion doesn't really make sense.. + */ + @Test + @Ignore + public void testExpandByValueSet() throws IOException { + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + ValueSet expanded = myValueSetDao.expand(toExpand, "11378"); + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + + assertThat(resp, not(containsString(""))); + } + + + @Test + public void testValiedateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { + IPrimitiveType display = null; + Coding coding = null; + CodeableConcept codeableConcept = null; + StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/v2-0487"); + StringType code = new StringType("BRN"); + StringType system = new StringType("http://hl7.org/fhir/v2/0487"); + ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); + + ourLog.info(result.getMessage()); + assertTrue(result.getMessage(), result.isResult()); + } + + +} + diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java new file mode 100644 index 00000000000..f74222a0406 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java @@ -0,0 +1,184 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.junit.AfterClass; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.util.TestUtil; + +public class FhirSearchDaoR4Test extends BaseJpaR4Test { + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Autowired + private IFulltextSearchSvc mySearchDao; + + @Test + public void testContentSearch() { + Long id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_h\u00F6ra"); + patient.addName().addGiven("AAAS"); + patient.addName().addGiven("CCC"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + Long id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_HORA"); + patient.addName().addGiven("AAAB"); + patient.addName().addGiven("CCC"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + Long id3; + { + Organization org = new Organization(); + org.setName("DDD"); + id3 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + + SearchParameterMap map = new SearchParameterMap(); + String resourceName = "Patient"; + + // One term + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); + + map.add(Constants.PARAM_CONTENT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1)); + } + // OR + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); + + map.add(Constants.PARAM_CONTENT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1, id2)); + } + // AND + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); + content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); + + map.add(Constants.PARAM_CONTENT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1)); + } + // AND OR + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAB")).addOr(new StringParam("AAAS"))); + content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); + + map.add(Constants.PARAM_CONTENT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1, id2)); + } + // All Resource Types + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("CCC")).addOr(new StringParam("DDD"))); + + map.add(Constants.PARAM_CONTENT, content); + List found = mySearchDao.search(null, map); + assertThat(found, containsInAnyOrder(id1, id2, id3)); + } + + } + + @Test + public void testNarrativeSearch() { + Long id1; + { + Patient patient = new Patient(); + patient.getText().setDivAsString("
AAAS

FOO

CCC
"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + Long id2; + { + Patient patient = new Patient(); + patient.getText().setDivAsString("
AAAB

FOO

CCC
"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + { + Patient patient = new Patient(); + patient.getText().setDivAsString("
ZZYZXY
"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPartAsLong(); + } + + SearchParameterMap map = new SearchParameterMap(); + String resourceName = "Patient"; + + // One term + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); + + map.add(Constants.PARAM_TEXT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1)); + } + // OR + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); + + map.add(Constants.PARAM_TEXT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1, id2)); + } + // AND + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); + content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); + + map.add(Constants.PARAM_TEXT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1)); + } + // AND OR + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("AAAB")).addOr(new StringParam("AAAS"))); + content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); + + map.add(Constants.PARAM_TEXT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, containsInAnyOrder(id1, id2)); + } + // Tag Contents + { + StringAndListParam content = new StringAndListParam(); + content.addAnd(new StringOrListParam().addOr(new StringParam("div"))); + + map.add(Constants.PARAM_TEXT, content); + List found = mySearchDao.search(resourceName, map); + assertThat(found, empty()); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4SearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4SearchTest.java new file mode 100644 index 00000000000..b29fc6a09b8 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4SearchTest.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.util.TestUtil; + +public class FhirSystemDaoR4SearchTest extends BaseJpaR4SystemTest { + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Test + public void testSearchByParans() { + // code to come.. just here to prevent a failure + } + + /*//@formatter:off + * [ERROR] Search parameter action has conflicting types token and reference + * [ERROR] Search parameter source has conflicting types token and reference + * [ERROR] Search parameter plan has conflicting types reference and token + * [ERROR] Search parameter version has conflicting types token and string + * [ERROR] Search parameter source has conflicting types reference and uri + * [ERROR] Search parameter location has conflicting types reference and uri + * [ERROR] Search parameter title has conflicting types string and token + * [ERROR] Search parameter manufacturer has conflicting types string and reference + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter source has conflicting types reference and string + * [ERROR] Search parameter destination has conflicting types reference and string + * [ERROR] Search parameter responsible has conflicting types reference and string + * [ERROR] Search parameter value has conflicting types token and string + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter action has conflicting types reference and token + * [ERROR] Search parameter version has conflicting types token and string + * [ERROR] Search parameter address has conflicting types token and string + * [ERROR] Search parameter base has conflicting types reference and token + * [ERROR] Search parameter target has conflicting types reference and token + * [ERROR] Search parameter base has conflicting types reference and uri + * [ERROR] Search parameter contact has conflicting types string and token + * [ERROR] Search parameter substance has conflicting types token and reference + * [ERROR] Search parameter provider has conflicting types reference and token + * [ERROR] Search parameter system has conflicting types token and uri + * [ERROR] Search parameter reference has conflicting types reference and uri + * //@formatter:off + */ +} 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 new file mode 100644 index 00000000000..1490b912f5f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -0,0 +1,2763 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.r4.model.*; +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.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.*; + +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoR4Test.class); + + @After + public void after() { + myDaoConfig.setAllowInlineMatchUrlReferences(false); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + } + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) { + + Patient pat = new Patient(); + pat + .addIdentifier() + .setSystem("http://acme.org") + .setValue("ID1"); + + Observation obs = new Observation(); + obs + .getCode() + .addCoding() + .setSystem("http://loinc.org") + .setCode("29463-7"); + obs.setEffective(new DateTimeType("2011-09-03T11:13:00-04:00")); + obs.setValue(new Quantity() + .setValue(new BigDecimal("123.4")) + .setCode("kg") + .setSystem("http://unitsofmeasure.org") + .setUnit("kg")); + obs.getSubject().setReference("urn:uuid:0001"); + + Observation obs2 = new Observation(); + obs2 + .getCode() + .addCoding() + .setSystem("http://loinc.org") + .setCode("29463-7"); + obs2.setEffective(new DateTimeType("2017-09-03T11:13:00-04:00")); + obs2.setValue(new Quantity() + .setValue(new BigDecimal("123.4")) + .setCode("kg") + .setSystem("http://unitsofmeasure.org") + .setUnit("kg")); + obs2.getSubject().setReference("urn:uuid:0001"); + + /* + * Put one observation before the patient it references, and + * one after it just to make sure that order doesn't matter + */ + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + + if (theVerb == HTTPVerb.PUT) { + input + .addEntry() + .setFullUrl("urn:uuid:0002") + .setResource(obs) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"); + input + .addEntry() + .setFullUrl("urn:uuid:0001") + .setResource(pat) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient?identifier=http%3A%2F%2Facme.org|ID1"); + input + .addEntry() + .setFullUrl("urn:uuid:0003") + .setResource(obs2) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2017-09-03T11:13:00-04:00"); + } else if (theVerb == HTTPVerb.POST) { + input + .addEntry() + .setFullUrl("urn:uuid:0002") + .setResource(obs) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Observation") + .setIfNoneExist("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"); + input + .addEntry() + .setFullUrl("urn:uuid:0001") + .setResource(pat) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient") + .setIfNoneExist("Patient?identifier=http%3A%2F%2Facme.org|ID1"); + input + .addEntry() + .setFullUrl("urn:uuid:0003") + .setResource(obs2) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Observation") + .setIfNoneExist("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2017-09-03T11:13:00-04:00"); + } + return input; + } + + @SuppressWarnings("unchecked") + private T find(Bundle theBundle, Class theType, int theIndex) { + int count = 0; + for (BundleEntryComponent nextEntry : theBundle.getEntry()) { + if (nextEntry.getResource() != null && theType.isAssignableFrom(nextEntry.getResource().getClass())) { + if (count == theIndex) { + T t = (T) nextEntry.getResource(); + return t; + } + count++; + } + } + fail(); + return null; + } + + @Test + public void testBatchCreateWithBadRead() { + Bundle request = new Bundle(); + request.setType(BundleType.BATCH); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("FOO"); + request + .addEntry() + .setResource(p) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + + request + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient/BABABABA"); + + Bundle response = mySystemDao.transaction(mySrd, request); + assertEquals(2, response.getEntry().size()); + + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*")); + assertEquals("404 Not Found", response.getEntry().get(1).getResponse().getStatus()); + + OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity()); + assertEquals("Resource Patient/BABABABA is not known", oo.getIssue().get(0).getDiagnostics()); + } + + @Test + public void testBatchCreateWithBadSearch() { + Bundle request = new Bundle(); + request.setType(BundleType.BATCH); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("FOO"); + request + .addEntry() + .setResource(p) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + + request + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?foobadparam=1"); + + Bundle response = mySystemDao.transaction(mySrd, request); + assertEquals(2, response.getEntry().size()); + + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*")); + assertEquals("400 Bad Request", response.getEntry().get(1).getResponse().getStatus()); + + OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity()); + assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter")); + } + + @Test + public void testCircularCreateAndDelete() { + Encounter enc = new Encounter(); + enc.setId(IdType.newRandomUuid()); + + Condition cond = new Condition(); + cond.setId(IdType.newRandomUuid()); + + EpisodeOfCare ep = new EpisodeOfCare(); + ep.setId(IdType.newRandomUuid()); + + enc.getEpisodeOfCareFirstRep().setReference(ep.getId()); + cond.getContext().setReference(enc.getId()); + ep.getDiagnosisFirstRep().getCondition().setReference(cond.getId()); + + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + inputBundle + .addEntry() + .setResource(ep) + .setFullUrl(ep.getId()) + .getRequest().setMethod(HTTPVerb.POST); + inputBundle + .addEntry() + .setResource(cond) + .setFullUrl(cond.getId()) + .getRequest().setMethod(HTTPVerb.POST); + inputBundle + .addEntry() + .setResource(enc) + .setFullUrl(enc.getId()) + .getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + IdType epId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + IdType condId = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + IdType encId = new IdType(resp.getEntry().get(2).getResponse().getLocation()); + + // Make sure a single one can't be deleted + try { + myEncounterDao.delete(encId); + fail(); + } catch (ResourceVersionConflictException e) { + // good + } + + /* + * Now delete all 3 by transaction + */ + inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(epId.toUnqualifiedVersionless().getValue()); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(encId.toUnqualifiedVersionless().getValue()); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(condId.toUnqualifiedVersionless().getValue()); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(inputBundle)); + resp = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + // They should now be deleted + try { + myEncounterDao.read(encId.toUnqualifiedVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + /** + * See #410 + */ + @Test + public void testContainedArePreservedForBug410() throws IOException { + String input = IOUtils.toString(getClass().getResourceAsStream("/bug-410-bundle.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + Bundle output = mySystemDao.transaction(mySrd, bundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + IdType id = new IdType(output.getEntry().get(1).getResponse().getLocation()); + MedicationRequest mo = myMedicationRequestDao.read(id); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(mo)); + } + + @Test + public void testDeleteWithHas() { + Observation obs1 = new Observation(); + obs1.setStatus(ObservationStatus.FINAL); + IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless(); + + DiagnosticReport rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + rpt.addResult(new Reference(obs2id)); + IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless(); + + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + + rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + + Bundle b = new Bundle(); + b.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER"); + b.addEntry().setResource(rpt).getRequest().setMethod(HTTPVerb.PUT).setUrl("DiagnosticReport?identifier=foo|IDENTIFIER"); + mySystemDao.transaction(mySrd, b); + + myObservationDao.read(obs1id); + try { + myObservationDao.read(obs2id); + fail(); + } catch (ResourceGoneException e) { + // good + } + + rpt = myDiagnosticReportDao.read(rptId); + assertThat(rpt.getResult(), empty()); + } + + @Test + public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForTransaction() { + Bundle bundle; + + // First time + Patient p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + // Second time should not result in an update + p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + // And third time should not result in an update + p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + myPatientDao.read(new IdType("Patient/A")); + myPatientDao.read(new IdType("Patient/A/_history/1")); + try { + myPatientDao.read(new IdType("Patient/A/_history/2")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + try { + myPatientDao.read(new IdType("Patient/A/_history/3")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + } + + @Test + public void testReindexing() { + Patient p = new Patient(); + p.addName().setFamily("family"); + final IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ValueSet vs = new ValueSet(); + vs.setUrl("http://foo"); + myValueSetDao.create(vs, mySrd); + + ResourceTable entity = new TransactionTemplate(myTxManager).execute(new TransactionCallback() { + @Override + public ResourceTable doInTransaction(TransactionStatus theStatus) { + return myEntityManager.find(ResourceTable.class, id.getIdPartAsLong()); + } + }); + assertEquals(Long.valueOf(1), entity.getIndexStatus()); + + mySystemDao.markAllResourcesForReindexing(); + + entity = new TransactionTemplate(myTxManager).execute(new TransactionCallback() { + @Override + public ResourceTable doInTransaction(TransactionStatus theStatus) { + return myEntityManager.find(ResourceTable.class, id.getIdPartAsLong()); + } + }); + assertEquals(null, entity.getIndexStatus()); + + mySystemDao.performReindexingPass(null); + + entity = new TransactionTemplate(myTxManager).execute(new TransactionCallback() { + @Override + public ResourceTable doInTransaction(TransactionStatus theStatus) { + return myEntityManager.find(ResourceTable.class, id.getIdPartAsLong()); + } + }); + assertEquals(Long.valueOf(1), entity.getIndexStatus()); + + // Just make sure this doesn't cause a choke + mySystemDao.performReindexingPass(100000); + + // Try making the resource unparseable + + TransactionTemplate template = new TransactionTemplate(myTxManager); + template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + template.execute(new TransactionCallback() { + @Override + public ResourceTable doInTransaction(TransactionStatus theStatus) { + ResourceTable table = myEntityManager.find(ResourceTable.class, id.getIdPartAsLong()); + table.setEncoding(ResourceEncodingEnum.JSON); + table.setIndexStatus(null); + try { + table.setResource("{\"resourceType\":\"FOO\"}".getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new Error(e); + } + myEntityManager.merge(table); + return null; + } + }); + + mySystemDao.performReindexingPass(null); + + entity = new TransactionTemplate(myTxManager).execute(new TransactionCallback() { + @Override + public ResourceTable doInTransaction(TransactionStatus theStatus) { + return myEntityManager.find(ResourceTable.class, id.getIdPartAsLong()); + } + }); + assertEquals(Long.valueOf(2), entity.getIndexStatus()); + + } + + @Test + public void testSystemMetaOperation() { + + Meta meta = mySystemDao.metaGetOperation(mySrd); + List published = meta.getTag(); + assertEquals(0, published.size()); + + String methodName = "testSystemMetaOperation"; + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + + patient.getMeta().addTag(null, "Dog", "Puppies"); + patient.getMeta().getSecurity().add(new Coding().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1")); + patient.getMeta().getProfile().add(new IdType("http://profile/1")); + + id1 = myPatientDao.create(patient, mySrd).getId(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(methodName); + patient.addName().setFamily("Tester").addGiven("Joe"); + + patient.getMeta().addTag("http://foo", "Cat", "Kittens"); + patient.getMeta().getSecurity().add(new Coding().setSystem("seclabel:sys:2").setCode("seclabel:code:2").setDisplay("seclabel:dis:2")); + patient.getMeta().getProfile().add(new IdType("http://profile/2")); + + myPatientDao.create(patient, mySrd); + } + + meta = mySystemDao.metaGetOperation(mySrd); + published = meta.getTag(); + assertEquals(2, published.size()); + assertEquals(null, published.get(0).getSystem()); + assertEquals("Dog", published.get(0).getCode()); + assertEquals("Puppies", published.get(0).getDisplay()); + assertEquals("http://foo", published.get(1).getSystem()); + assertEquals("Cat", published.get(1).getCode()); + assertEquals("Kittens", published.get(1).getDisplay()); + List secLabels = meta.getSecurity(); + assertEquals(2, secLabels.size()); + assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:1", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:1", secLabels.get(0).getDisplayElement().getValue()); + assertEquals("seclabel:sys:2", secLabels.get(1).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(1).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(1).getDisplayElement().getValue()); + List profiles = meta.getProfile(); + assertEquals(2, profiles.size()); + assertEquals("http://profile/1", profiles.get(0).getValue()); + assertEquals("http://profile/2", profiles.get(1).getValue()); + + myPatientDao.removeTag(id1, TagTypeEnum.TAG, null, "Dog", mySrd); + myPatientDao.removeTag(id1, TagTypeEnum.SECURITY_LABEL, "seclabel:sys:1", "seclabel:code:1", mySrd); + myPatientDao.removeTag(id1, TagTypeEnum.PROFILE, BaseHapiFhirDao.NS_JPA_PROFILE, "http://profile/1", mySrd); + + meta = mySystemDao.metaGetOperation(mySrd); + published = meta.getTag(); + assertEquals(1, published.size()); + assertEquals("http://foo", published.get(0).getSystem()); + assertEquals("Cat", published.get(0).getCode()); + assertEquals("Kittens", published.get(0).getDisplay()); + secLabels = meta.getSecurity(); + assertEquals(1, secLabels.size()); + assertEquals("seclabel:sys:2", secLabels.get(0).getSystemElement().getValue()); + assertEquals("seclabel:code:2", secLabels.get(0).getCodeElement().getValue()); + assertEquals("seclabel:dis:2", secLabels.get(0).getDisplayElement().getValue()); + profiles = meta.getProfile(); + assertEquals(1, profiles.size()); + assertEquals("http://profile/2", profiles.get(0).getValue()); + + } + + @Test + public void testTransaction1() throws IOException { + String inputBundleString = loadClasspath("/david-bundle-error.json"); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputBundleString); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + } + + @Test + public void testTransactionBatchWithFailingRead() { + String methodName = "testTransactionBatchWithFailingRead"; + Bundle request = new Bundle(); + request.setType(BundleType.BATCH); + + Patient p = new Patient(); + p.addName().setFamily(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient/THIS_ID_DOESNT_EXIST"); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + assertEquals(BundleType.BATCHRESPONSE, resp.getTypeElement().getValue()); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + BundleEntryResponseComponent respEntry; + + // Bundle.entry[0] is create response + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/")); + + // Bundle.entry[1] is failed read response + Resource oo = resp.getEntry().get(1).getResponse().getOutcome(); + assertEquals(OperationOutcome.class, oo.getClass()); + assertEquals(IssueSeverity.ERROR, ((OperationOutcome) oo).getIssue().get(0).getSeverityElement().getValue()); + assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) oo).getIssue().get(0).getDiagnostics()); + assertEquals("404 Not Found", resp.getEntry().get(1).getResponse().getStatus()); + + // Check POST + respEntry = resp.getEntry().get(0).getResponse(); + assertEquals("201 Created", respEntry.getStatus()); + IdType createdId = new IdType(respEntry.getLocation()); + assertEquals("Patient", createdId.getResourceType()); + myPatientDao.read(createdId, mySrd); // shouldn't fail + + // Check GET + respEntry = resp.getEntry().get(1).getResponse(); + assertThat(respEntry.getStatus(), startsWith("404")); + + } + + @Test + public void testTransactionCreateInlineMatchUrlWithNoMatches() { + String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithNoMatches\" - No resources match this search", e.getMessage()); + } + } + + @Test + public void testTransactionCreateInlineMatchUrlWithOneMatch() { + String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + assertEquals("1", o.getIdElement().getVersionIdPart()); + + } + + @Test + public void testTransactionCreateInlineMatchUrlWithOneMatch2() { + String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch2"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addName().addGiven("Heute"); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/?given=heute"); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + assertEquals("1", o.getIdElement().getVersionIdPart()); + + } + + @Test + public void testTransactionCreateInlineMatchUrlWithOneMatchLastUpdated() { + Bundle request = new Bundle(); + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Observation?_lastUpdated=gt2011-01-01"); + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + /* + * Second time should not update + */ + + request = new Bundle(); + o = new Observation(); + o.getCode().setText("Some Observation"); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Observation?_lastUpdated=gt2011-01-01"); + resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + } + + @Test + public void testTransactionCreateInlineMatchUrlWithTwoMatches() { + String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithTwoMatches\" - Multiple resources match this search", e.getMessage()); + } + } + + @Test + public void testTransactionCreateMatchUrlWithOneMatch() { + String methodName = "testTransactionCreateMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), endsWith("Patient/" + id.getIdPart() + "/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + assertEquals("1", o.getIdElement().getVersionIdPart()); + + } + + @Test + public void testTransactionCreateMatchUrlWithTwoMatch() { + String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("with match URL \"Patient")); + } + } + + @Test + public void testTransactionCreateMatchUrlWithZeroMatch() { + String methodName = "testTransactionCreateMatchUrlWithZeroMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + String patientId = respEntry.getResponse().getLocation(); + assertThat(patientId, not(endsWith("Patient/" + methodName + "/_history/1"))); + assertThat(patientId, (endsWith("/_history/1"))); + assertThat(patientId, (containsString("Patient/"))); + assertEquals("1", respEntry.getResponse().getEtag()); + + respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(new IdType(patientId).toUnqualifiedVersionless().getValue(), o.getSubject().getReference()); + } + + @Test + public void testTransactionCreateNoMatchUrl() { + String methodName = "testTransactionCreateNoMatchUrl"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + String patientId = respEntry.getResponse().getLocation(); + assertThat(patientId, not(containsString("test"))); + + /* + * Interceptor should have been called once for the transaction, and once for the embedded operation + */ + ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.TRANSACTION), detailsCapt.capture()); + ActionRequestDetails details = detailsCapt.getValue(); + assertEquals("Bundle", details.getResourceType()); + assertEquals(Bundle.class, details.getResource().getClass()); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); + details = detailsCapt.getValue(); + assertNotNull(details.getId()); + assertEquals("Patient", details.getResourceType()); + assertEquals(Patient.class, details.getResource().getClass()); + + } + + @Test + public void testTransactionCreateWithBadRead() { + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("FOO"); + request + .addEntry() + .setResource(p) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + + request + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient/BABABABA"); + + Bundle response = mySystemDao.transaction(mySrd, request); + assertEquals(2, response.getEntry().size()); + + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*")); + assertEquals("404 Not Found", response.getEntry().get(1).getResponse().getStatus()); + + OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity()); + assertEquals("Resource Patient/BABABABA is not known", oo.getIssue().get(0).getDiagnostics()); + } + + @Test + public void testTransactionCreateWithBadSearch() { + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("FOO"); + request + .addEntry() + .setResource(p) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + + request + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?foobadparam=1"); + + Bundle response = mySystemDao.transaction(mySrd, request); + assertEquals(2, response.getEntry().size()); + + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*")); + assertEquals("400 Bad Request", response.getEntry().get(1).getResponse().getStatus()); + + OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity()); + assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter")); + } + + @Test + public void testTransactionCreateWithDuplicateMatchUrl01() { + String methodName = "testTransactionCreateWithDuplicateMatchUrl01"; + Bundle request = new Bundle(); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals(e.getMessage(), + "Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?"); + } + } + + @Test + public void testTransactionCreateWithDuplicateMatchUrl02() { + String methodName = "testTransactionCreateWithDuplicateMatchUrl02"; + Bundle request = new Bundle(); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals(e.getMessage(), + "Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl02\". Does transaction request contain duplicates?"); + } + } + + @Test + public void testTransactionCreateWithInvalidMatchUrl() { + String methodName = "testTransactionCreateWithInvalidMatchUrl"; + Bundle request = new Bundle(); + + Patient p; + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + BundleEntryRequestComponent entry = request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + entry.setIfNoneExist("Patient?identifier identifier" + methodName); + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Failed to parse match URL[Patient?identifier identifiertestTransactionCreateWithInvalidMatchUrl] - URL is invalid (must not contain spaces)", e.getMessage()); + } + + try { + entry.setIfNoneExist("Patient?identifier="); + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid match URL[Patient?identifier=] - URL has no search parameters", e.getMessage()); + } + + try { + entry.setIfNoneExist("Patient?foo=bar"); + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Failed to parse match URL[Patient?foo=bar] - Resource type Patient does not have a parameter with name: foo", e.getMessage()); + } + } + + + @Test + public void testTransactionCreateWithInvalidReferenceNumeric() { + String methodName = "testTransactionCreateWithInvalidReferenceNumeric"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.getManagingOrganization().setReference("Organization/9999999999999999"); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Resource Organization/9999999999999999 not found, specified in path: Patient.managingOrganization")); + } + } + + @Test + public void testTransactionCreateWithInvalidReferenceTextual() { + String methodName = "testTransactionCreateWithInvalidReferenceTextual"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.getManagingOrganization().setReference("Organization/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Resource Organization/" + methodName + " not found, specified in path: Patient.managingOrganization")); + } + } + + @Test + public void testTransactionCreateWithPutUsingAbsoluteUrl() { + String methodName = "testTransactionCreateWithPutUsingAbsoluteUrl"; + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("http://localhost/server/base/Patient/" + methodName); + + mySystemDao.transaction(mySrd, request); + + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + } + + @Test + public void testTransactionCreateWithPutUsingUrl() { + String methodName = "testTransactionCreateWithPutUsingUrl"; + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Observation o = new Observation(); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation/a" + methodName); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/" + methodName); + + mySystemDao.transaction(mySrd, request); + + myObservationDao.read(new IdType("Observation/a" + methodName), mySrd); + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + } + + @Test + public void testTransactionCreateWithPutUsingUrl2() throws Exception { + String req = IOUtils.toString(FhirSystemDaoR4Test.class.getResourceAsStream("/bundle-r4.xml"), StandardCharsets.UTF_8); + Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, req); + mySystemDao.transaction(mySrd, request); + } + + @Test + public void testTransactionDeleteByResourceId() { + String methodName = "testTransactionDeleteByResourceId"; + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id1 = myPatientDao.create(p1, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id1); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue(methodName); + p2.setId("Patient/" + methodName); + IIdType id2 = myPatientDao.update(p2, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id2); + + Bundle request = new Bundle(); + + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient/" + id1.getIdPart()); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient/" + id2.getIdPart()); + + myPatientDao.read(id1.toVersionless(), mySrd); + myPatientDao.read(id2.toVersionless(), mySrd); + + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(2, resp.getEntry().size()); + assertEquals("204 No Content", resp.getEntry().get(0).getResponse().getStatus()); + assertEquals("204 No Content", resp.getEntry().get(1).getResponse().getStatus()); + + try { + myPatientDao.read(id1.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myPatientDao.read(id2.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + /** + * See #253 Test that the order of deletes is version independent + */ + @Test + public void testTransactionDeleteIsOrderIndependantTargetFirst() { + String methodName = "testTransactionDeleteIsOrderIndependantTargetFirst"; + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType pid = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); + ourLog.info("Created patient, got it: {}", pid); + + Observation o1 = new Observation(); + o1.getSubject().setReferenceElement(pid); + IIdType oid1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.addIdentifier().setValue(methodName); + o2.getSubject().setReferenceElement(pid); + IIdType oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + myPatientDao.read(pid, mySrd); + myObservationDao.read(oid1, mySrd); + + // The target is Patient, so try with it first in the bundle + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(pid.getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(oid1.getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Observation?identifier=" + methodName); + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(3, resp.getEntry().size()); + assertEquals("204 No Content", resp.getEntry().get(0).getResponse().getStatus()); + assertEquals("204 No Content", resp.getEntry().get(1).getResponse().getStatus()); + assertEquals("204 No Content", resp.getEntry().get(2).getResponse().getStatus()); + + try { + myPatientDao.read(pid, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myObservationDao.read(oid1, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myObservationDao.read(oid2, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + /** + * See #253 Test that the order of deletes is version independent + */ + @Test + public void testTransactionDeleteIsOrderIndependantTargetLast() { + String methodName = "testTransactionDeleteIsOrderIndependantTargetFirst"; + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType pid = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); + ourLog.info("Created patient, got it: {}", pid); + + Observation o1 = new Observation(); + o1.getSubject().setReferenceElement(pid); + IIdType oid1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.addIdentifier().setValue(methodName); + o2.getSubject().setReferenceElement(pid); + IIdType oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); + + myPatientDao.read(pid, mySrd); + myObservationDao.read(oid1, mySrd); + + // The target is Patient, so try with it last in the bundle + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(oid1.getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Observation?identifier=" + methodName); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(pid.getValue()); + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(3, resp.getEntry().size()); + assertEquals("204 No Content", resp.getEntry().get(0).getResponse().getStatus()); + assertEquals("204 No Content", resp.getEntry().get(1).getResponse().getStatus()); + assertEquals("204 No Content", resp.getEntry().get(2).getResponse().getStatus()); + + try { + myPatientDao.read(pid, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myObservationDao.read(oid1, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + myObservationDao.read(oid2, mySrd); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + @Test + public void testTransactionDeleteMatchUrlWithOneMatch() { + String methodName = "testTransactionDeleteMatchUrlWithOneMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent nextEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT + " No Content", nextEntry.getResponse().getStatus()); + + try { + myPatientDao.read(id.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // ok + } + + try { + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + fail(); + } catch (ResourceNotFoundException e) { + // ok + } + + IBundleProvider history = myPatientDao.history(id, null, null, mySrd); + assertEquals(2, history.size().intValue()); + + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(0, 1).get(0))); + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(0, 1).get(0)).getValue()); + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) history.getResources(1, 2).get(0))); + + } + + @Test + public void testTransactionDeleteMatchUrlWithTwoMatch() { + myDaoConfig.setAllowMultipleDelete(false); + + String methodName = "testTransactionDeleteMatchUrlWithTwoMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("resource with match URL \"Patient?")); + } + } + + @Test + public void testTransactionDeleteMatchUrlWithZeroMatch() { + String methodName = "testTransactionDeleteMatchUrlWithZeroMatch"; + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + // try { + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + assertEquals("204 No Content", resp.getEntry().get(0).getResponse().getStatus()); + + // fail(); + // } catch (ResourceNotFoundException e) { + // assertThat(e.getMessage(), containsString("resource matching URL \"Patient?")); + // } + } + + @Test + public void testTransactionDeleteNoMatchUrl() { + String methodName = "testTransactionDeleteNoMatchUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle res = mySystemDao.transaction(mySrd, request); + assertEquals(1, res.getEntry().size()); + + assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT + " No Content", res.getEntry().get(0).getResponse().getStatus()); + + try { + myPatientDao.read(id.toVersionless(), mySrd); + fail(); + } catch (ResourceGoneException e) { + // ok + } + } + + @Test + public void testTransactionDoesNotAllowDanglingTemporaryIds() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + + BundleEntryComponent entry = bundle.addEntry(); + Patient p = new Patient(); + p.getManagingOrganization().setReference("urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5"); + entry.setResource(p); + entry.getRequest().setMethod(HTTPVerb.POST); + entry.getRequest().setUrl("Patient"); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to satisfy placeholder ID: urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5", e.getMessage()); + } + } + + @Test + public void testTransactionDoesNotLeavePlaceholderIds() throws Exception { + String input; + try { + input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); + } catch (IOException e) { + fail(e.toString()); + return; + } + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + mySystemDao.transaction(mySrd, bundle); + + IBundleProvider history = mySystemDao.history(null, null, null); + Bundle list = toBundleR4(history); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(list)); + + assertEquals(6, list.getEntry().size()); + + Patient p = find(list, Patient.class, 0); + assertTrue(p.getIdElement().isIdPartValidLong()); + assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong()); + } + + @Test + public void testTransactionDoubleConditionalCreateOnlyCreatesOne() { + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + + Encounter enc1 = new Encounter(); + enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc1) + .getRequest() + .setMethod(HTTPVerb.POST) + .setIfNoneExist("Encounter?identifier=urn:foo|12345"); + Encounter enc2 = new Encounter(); + enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc2) + .getRequest() + .setMethod(HTTPVerb.POST) + .setIfNoneExist("Encounter?identifier=urn:foo|12345"); + + try { + mySystemDao.transaction(mySrd, inputBundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", + e.getMessage()); + } + } + + @Test + public void testTransactionDoubleConditionalUpdateOnlyCreatesOne() { + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + + Encounter enc1 = new Encounter(); + enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc1) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Encounter?identifier=urn:foo|12345"); + Encounter enc2 = new Encounter(); + enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc2) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Encounter?identifier=urn:foo|12345"); + + try { + mySystemDao.transaction(mySrd, inputBundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", + e.getMessage()); + } + + } + + @Test(expected = InvalidRequestException.class) + public void testTransactionFailsWithDuplicateIds() { + Bundle request = new Bundle(); + + Patient patient1 = new Patient(); + patient1.setId(new IdType("Patient/testTransactionFailsWithDusplicateIds")); + patient1.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); + request.addEntry().setResource(patient1).getRequest().setMethod(HTTPVerb.POST); + + Patient patient2 = new Patient(); + patient2.setId(new IdType("Patient/testTransactionFailsWithDusplicateIds")); + patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); + request.addEntry().setResource(patient2).getRequest().setMethod(HTTPVerb.POST); + + mySystemDao.transaction(mySrd, request); + } + + @Test + public void testTransactionFromBundle() throws Exception { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/transaction_link_patient_eve.xml"); + String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr); + + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/a555-44-4444/_history/")); + assertThat(resp.getEntry().get(1).getResponse().getLocation(), startsWith("Patient/temp6789/_history/")); + assertThat(resp.getEntry().get(2).getResponse().getLocation(), startsWith("Organization/GHH/_history/")); + + Patient p = myPatientDao.read(new IdType("Patient/a555-44-4444/_history/1"), mySrd); + assertEquals("Patient/temp6789", p.getLink().get(0).getOther().getReference()); + } + + @Test + public void testTransactionFromBundle2() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/transaction-bundle.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + Bundle response = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); + + /* + * Now a second time + */ + + bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + response = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); + + } + + @Test + public void testTransactionFromBundle6() throws Exception { + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + Bundle output = mySystemDao.transaction(mySrd, myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle)); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + } + + @Test + public void testTransactionFromBundleJosh() throws Exception { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/josh-bundle.json"); + String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, bundleStr); + + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus()); + } + + @Test + public void testTransactionOrdering() { + String methodName = "testTransactionOrdering"; + + //@formatter:off + /* + * Transaction Order, per the spec: + * + * Process any DELETE interactions + * Process any POST interactions + * Process any PUT interactions + * Process any GET interactions + * + * This test creates a transaction bundle that includes + * these four operations in the reverse order and verifies + * that they are invoked correctly. + */ + //@formatter:off + + int pass = 0; + IdType patientPlaceholderId = IdType.newRandomUuid(); + + Bundle req = testTransactionOrderingCreateBundle(methodName, pass, patientPlaceholderId); + Bundle resp = mySystemDao.transaction(mySrd, req); + testTransactionOrderingValidateResponse(pass, resp); + + pass = 1; + patientPlaceholderId = IdType.newRandomUuid(); + + req = testTransactionOrderingCreateBundle(methodName, pass, patientPlaceholderId); + resp = mySystemDao.transaction(mySrd, req); + testTransactionOrderingValidateResponse(pass, resp); + + } + + private Bundle testTransactionOrderingCreateBundle(String methodName, int pass, IdType patientPlaceholderId) { + Bundle req = new Bundle(); + req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?identifier=" + methodName); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(patientPlaceholderId); + obs.addIdentifier().setValue(methodName); + obs.getCode().setText(methodName + pass); + req.addEntry().setResource(obs).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation?identifier=" + methodName); + + Patient pat = new Patient(); + pat.addIdentifier().setValue(methodName); + pat.addName().setFamily(methodName + pass); + req.addEntry().setResource(pat).setFullUrl(patientPlaceholderId.getValue()).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + req.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=" + methodName); + return req; + } + + private void testTransactionOrderingValidateResponse(int pass, Bundle resp) { + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + assertEquals(4, resp.getEntry().size()); + assertEquals("200 OK", resp.getEntry().get(0).getResponse().getStatus()); + if (pass == 0) { + assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus()); + assertThat(resp.getEntry().get(1).getResponse().getLocation(), startsWith("Observation/")); + assertThat(resp.getEntry().get(1).getResponse().getLocation(), endsWith("_history/1")); + } else { + assertEquals("200 OK", resp.getEntry().get(1).getResponse().getStatus()); + assertThat(resp.getEntry().get(1).getResponse().getLocation(), startsWith("Observation/")); + assertThat(resp.getEntry().get(1).getResponse().getLocation(), endsWith("_history/2")); + } + assertEquals("201 Created", resp.getEntry().get(2).getResponse().getStatus()); + assertThat(resp.getEntry().get(2).getResponse().getLocation(), startsWith("Patient/")); + if (pass == 0) { + assertEquals("204 No Content", resp.getEntry().get(3).getResponse().getStatus()); + } else { + assertEquals("204 No Content", resp.getEntry().get(3).getResponse().getStatus()); + } + + + Bundle respGetBundle = (Bundle) resp.getEntry().get(0).getResource(); + assertEquals(1, respGetBundle.getEntry().size()); + assertEquals("testTransactionOrdering" + pass, ((Patient)respGetBundle.getEntry().get(0).getResource()).getName().get(0).getFamily()); + assertThat(respGetBundle.getLink("self").getUrl(), endsWith("/Patient?identifier=testTransactionOrdering")); + } + + @Test + public void testTransactionOruBundle() throws IOException { + myDaoConfig.setAllowMultipleDelete(true); + + String input = IOUtils.toString(getClass().getResourceAsStream("/oruBundle.json"), StandardCharsets.UTF_8); + + Bundle inputBundle; + Bundle outputBundle; + inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + IBundleProvider allPatients = myPatientDao.search(new SearchParameterMap()); + assertEquals(1, allPatients.size().intValue()); + } + + @Test + public void testTransactionReadAndSearch() { + String methodName = "testTransactionReadAndSearch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType idv1 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got id: {}", idv1); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Family Name"); + p.setId("Patient/" + methodName); + IIdType idv2 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Updated patient, got id: {}", idv2); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl(idv1.toUnqualified().getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(3, resp.getEntry().size()); + + BundleEntryComponent nextEntry; + + nextEntry = resp.getEntry().get(0); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv2.toUnqualified(), nextEntry.getResource().getIdElement().toUnqualified()); + + nextEntry = resp.getEntry().get(1); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv1.toUnqualified(), nextEntry.getResource().getIdElement().toUnqualified()); + + nextEntry = resp.getEntry().get(2); + assertEquals(Bundle.class, nextEntry.getResource().getClass()); + Bundle respBundle = (Bundle) nextEntry.getResource(); + assertEquals(1, respBundle.getTotal()); + + /* + * Interceptor should have been called once for the transaction, and once for the embedded operation + */ + ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.TRANSACTION), detailsCapt.capture()); + ActionRequestDetails details = detailsCapt.getValue(); + assertEquals("Bundle", details.getResourceType()); + assertEquals(Bundle.class, details.getResource().getClass()); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.READ), detailsCapt.capture()); + details = detailsCapt.getValue(); + assertEquals(idv1.toUnqualifiedVersionless().getValue(), details.getId().getValue()); + assertEquals("Patient", details.getResourceType()); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.VREAD), detailsCapt.capture()); + details = detailsCapt.getValue(); + assertEquals(idv1.toUnqualified().getValue(), details.getId().getValue()); + assertEquals("Patient", details.getResourceType()); + + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + verify(myInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.SEARCH_TYPE), detailsCapt.capture()); + details = detailsCapt.getValue(); + assertEquals("Patient", details.getResourceType()); + + + } + + @Test + public void testTransactionReadWithIfNoneMatch() { + String methodName = "testTransactionReadWithIfNoneMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType idv1 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got id: {}", idv1); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Family Name"); + p.setId("Patient/" + methodName); + IIdType idv2 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Updated patient, got id: {}", idv2); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()).setIfNoneMatch("W/\"" + idv1.getVersionIdPart() + "\""); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()).setIfNoneMatch("W/\"" + idv2.getVersionIdPart() + "\""); + + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(3, resp.getEntry().size()); + + BundleEntryComponent nextEntry; + + nextEntry = resp.getEntry().get(0); + assertNotNull(nextEntry.getResource()); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv2.toUnqualified(), nextEntry.getResource().getIdElement().toUnqualified()); + assertEquals("200 OK", nextEntry.getResponse().getStatus()); + + nextEntry = resp.getEntry().get(1); + assertNotNull(nextEntry.getResource()); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv2.toUnqualified(), nextEntry.getResource().getIdElement().toUnqualified()); + assertEquals("200 OK", nextEntry.getResponse().getStatus()); + + nextEntry = resp.getEntry().get(2); + assertEquals("304 Not Modified", nextEntry.getResponse().getStatus()); + assertNull(nextEntry.getResource()); + } + + @Test + public void testTransactionSearchWithCount() { + String methodName = "testTransactionSearchWithCount"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType idv1 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got id: {}", idv1); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Family Name"); + p.setId("Patient/" + methodName); + IIdType idv2 = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Updated patient, got id: {}", idv2); + + Bundle request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "=1"); + Bundle resp = mySystemDao.transaction(mySrd, request); + + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent nextEntry = resp.getEntry().get(0); + assertEquals(Bundle.class, nextEntry.getResource().getClass()); + Bundle respBundle = (Bundle) nextEntry.getResource(); + assertThat(respBundle.getTotal(), greaterThan(0)); + + // Invalid _count + + request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "=GKJGKJG"); + try { + mySystemDao.transaction(mySrd, request); + } catch (InvalidRequestException e) { + assertEquals(e.getMessage(), ("Invalid _count value: GKJGKJG")); + } + + // Empty _count + + request = new Bundle(); + request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "="); + respBundle = mySystemDao.transaction(mySrd, request); + assertThat(respBundle.getEntry().size(), greaterThan(0)); + } + + @Test + public void testTransactionSingleEmptyResource() { + + Bundle request = new Bundle(); + request.setType(BundleType.SEARCHSET); + Patient p = new Patient(); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process transaction where incoming Bundle.type = searchset", e.getMessage()); + } + + } + + @Test + public void testTransactionUpdateMatchUrlWithOneMatch() { + String methodName = "testTransactionUpdateMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent nextEntry = resp.getEntry().get(0); + assertEquals("200 OK", nextEntry.getResponse().getStatus()); + assertThat(nextEntry.getResponse().getLocation(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); + assertNotEquals(id, p.getId()); + assertThat(p.getId().toString(), endsWith("/_history/2")); + + nextEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_200_OK + " OK", nextEntry.getResponse().getStatus()); + assertThat(nextEntry.getResponse().getLocation(), not(emptyString())); + + nextEntry = resp.getEntry().get(1); + o = myObservationDao.read(new IdType(nextEntry.getResponse().getLocation()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + + } + + @Test + public void testTransactionUpdateMatchUrlWithTwoMatch() { + String methodName = "testTransactionUpdateMatchUrlWithTwoMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("with match URL \"Patient")); + } + } + + @Test + public void testTransactionUpdateMatchUrlWithZeroMatch() { + String methodName = "testTransactionUpdateMatchUrlWithZeroMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addName().setFamily("Hello"); + IIdType id = myPatientDao.create(p, mySrd).getId(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent nextEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", nextEntry.getResponse().getStatus()); + IdType patientId = new IdType(nextEntry.getResponse().getLocation()); + + assertThat(nextEntry.getResponse().getLocation(), not(containsString("test"))); + assertNotEquals(id.toVersionless(), patientId.toVersionless()); + + assertThat(patientId.getValue(), endsWith("/_history/1")); + + nextEntry = resp.getEntry().get(1); + o = myObservationDao.read(new IdType(nextEntry.getResponse().getLocation()), mySrd); + assertEquals(patientId.toVersionless().getValue(), o.getSubject().getReference()); + + } + + @Test + public void testTransactionUpdateNoMatchUrl() { + String methodName = "testTransactionUpdateNoMatchUrl"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/" + id.getIdPart()); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent nextEntry = resp.getEntry().get(0); + assertEquals("200 OK", nextEntry.getResponse().getStatus()); + + assertThat(nextEntry.getResponse().getLocation(), (containsString("test"))); + assertEquals(id.toVersionless(), new IdType(nextEntry.getResponse().getLocation()).toVersionless()); + assertNotEquals(id, new IdType(nextEntry.getResponse().getLocation())); + assertThat(nextEntry.getResponse().getLocation(), endsWith("/_history/2")); + + nextEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", nextEntry.getResponse().getStatus()); + + o = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + + } + + /** + * Format changed, source isn't valid + */ + @Test + @Ignore + public void testTransactionWithBundledValidationSourceAndTarget() throws Exception { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml"); + String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr); + + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(encoded); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); + //@formatter:off + assertThat(encoded, containsString("\"response\":{" + + "\"status\":\"201 Created\"," + + "\"location\":\"Questionnaire/54127-6/_history/1\",")); + //@formatter:on + + /* + * Upload again to update + */ + + resp = mySystemDao.transaction(mySrd, bundle); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(encoded); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); + //@formatter:off + assertThat(encoded, containsString("\"response\":{" + + "\"status\":\"200 OK\"," + + "\"location\":\"Questionnaire/54127-6/_history/2\",")); + //@formatter:on + + } + + @Test + public void testTransactionWithInlineMatchUrl() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + Bundle response = mySystemDao.transaction(mySrd, bundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + + } + + @Test + public void testTransactionWithInlineMatchUrlMultipleMatches() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - Multiple resources match this search", e.getMessage()); + } + + } + + @Test + public void testTransactionWithInlineMatchUrlNoMatches() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - No resources match this search", e.getMessage()); + } + + } + + @Test + public void testTransactionWIthInvalidPlaceholder() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Observation o1 = new Observation(); + o1.setId("cid:observation1"); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + try { + mySystemDao.transaction(mySrd, res); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid placeholder ID found: cid:observation1 - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", e.getMessage()); + } + } + + @Test + public void testTransactionWithInvalidType() { + Bundle request = new Bundle(); + request.setType(BundleType.SEARCHSET); + Patient p = new Patient(); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process transaction where incoming Bundle.type = searchset", e.getMessage()); + } + + } + + @Test + public void testTransactionWithMultiBundle() throws IOException { + String inputBundleString = loadClasspath("/batch-error.xml"); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, inputBundleString); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + Set values = new HashSet(); + for (ResourceTag next : myResourceTagDao.findAll()) { + if (!values.add(next.toString())) { + ourLog.info("Found duplicate tag on resource of type {}", next.getResource().getResourceType()); + ourLog.info("Tag was: {} / {}", next.getTag().getSystem(), next.getTag().getCode()); + } + } + } + }); + + } + + @Test + public void testTransactionWithNullReference() { + Patient p = new Patient(); + p.addName().setFamily("family"); + final IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Bundle inputBundle = new Bundle(); + + //@formatter:off + Patient app0 = new Patient(); + app0.addName().setFamily("NEW PATIENT"); + String placeholderId0 = IdDt.newRandomUuid().getValue(); + inputBundle + .addEntry() + .setResource(app0) + .setFullUrl(placeholderId0) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + //@formatter:on + + //@formatter:off + Appointment app1 = new Appointment(); + app1.addParticipant().getActor().setReference(id.getValue()); + inputBundle + .addEntry() + .setResource(app1) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Appointment"); + //@formatter:on + + //@formatter:off + Appointment app2 = new Appointment(); + app2.addParticipant().getActor().setDisplay("NO REF"); + app2.addParticipant().getActor().setDisplay("YES REF").setReference(placeholderId0); + inputBundle + .addEntry() + .setResource(app2) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Appointment"); + //@formatter:on + + Bundle outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + assertEquals(3, outputBundle.getEntry().size()); + IdDt id0 = new IdDt(outputBundle.getEntry().get(0).getResponse().getLocation()); + IdDt id2 = new IdDt(outputBundle.getEntry().get(2).getResponse().getLocation()); + + app2 = myAppointmentDao.read(id2, mySrd); + assertEquals("NO REF", app2.getParticipant().get(0).getActor().getDisplay()); + assertEquals(null, app2.getParticipant().get(0).getActor().getReference()); + assertEquals("YES REF", app2.getParticipant().get(1).getActor().getDisplay()); + assertEquals(id0.toUnqualifiedVersionless().getValue(), app2.getParticipant().get(1).getActor().getReference()); + } + + /* + * Make sure we are able to handle placeholder IDs in match URLs, e.g. + * + * "request": { + * "method": "PUT", + * "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00" + * } + * + */ + @Test + public void testTransactionWithPlaceholderIdInMatchUrlPost() { + + Bundle input = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.POST); + Bundle output = mySystemDao.transaction(null, input); + + assertEquals("201 Created", output.getEntry().get(0).getResponse().getStatus()); + assertEquals("201 Created", output.getEntry().get(1).getResponse().getStatus()); + assertEquals("201 Created", output.getEntry().get(2).getResponse().getStatus()); + + Bundle input2 = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.POST); + Bundle output2 = mySystemDao.transaction(null, input2); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output2)); + + assertEquals("200 OK", output2.getEntry().get(0).getResponse().getStatus()); + assertEquals("200 OK", output2.getEntry().get(1).getResponse().getStatus()); + assertEquals("200 OK", output2.getEntry().get(2).getResponse().getStatus()); + + } + + /* + * Make sure we are able to handle placeholder IDs in match URLs, e.g. + * + * "request": { + * "method": "PUT", + * "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00" + * } + * + */ + @Test + public void testTransactionWithPlaceholderIdInMatchUrlPut() { + + Bundle input = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.PUT); + Bundle output = mySystemDao.transaction(null, input); + + assertEquals("201 Created", output.getEntry().get(0).getResponse().getStatus()); + assertEquals("201 Created", output.getEntry().get(1).getResponse().getStatus()); + assertEquals("201 Created", output.getEntry().get(2).getResponse().getStatus()); + + Bundle input2 = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.PUT); + Bundle output2 = mySystemDao.transaction(null, input2); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output2)); + + assertEquals("200 OK", output2.getEntry().get(0).getResponse().getStatus()); + assertEquals("200 OK", output2.getEntry().get(1).getResponse().getStatus()); + assertEquals("200 OK", output2.getEntry().get(2).getResponse().getStatus()); + + } + + /** + * Per a message on the mailing list + */ + @Test + public void testTransactionWithPostDoesntUpdate() throws Exception { + + // First bundle (name is Joshua) + + String input = IOUtils.toString(getClass().getResource("/r4-post1.xml"), StandardCharsets.UTF_8); + Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + Bundle response = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + + assertEquals(1, response.getEntry().size()); + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); + String id = response.getEntry().get(0).getResponse().getLocation(); + + // Now the second (name is Adam, shouldn't get used) + + input = IOUtils.toString(getClass().getResource("/r4-post2.xml"), StandardCharsets.UTF_8); + request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + response = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + + assertEquals(1, response.getEntry().size()); + assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); + assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); + String id2 = response.getEntry().get(0).getResponse().getLocation(); + assertEquals(id, id2); + + Patient patient = myPatientDao.read(new IdType(id), mySrd); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient)); + assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString()); + } + + + @Test + public void testTransactionWithReferenceResource() { + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.setActive(true); + p.setId(IdType.newRandomUuid()); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId()); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setResource(p); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue(); + assertThat(patientId, startsWith("Patient/")); + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("subject", new ReferenceParam(patientId)); + IBundleProvider found = myObservationDao.search(params); + assertEquals(1, found.size().intValue()); + } + + + @Test + public void testTransactionWithReferenceToCreateIfNoneExist() { + Bundle bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + + Medication med = new Medication(); + IdType medId = IdType.newRandomUuid(); + med.setId(medId); + med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); + bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); + + MedicationRequest mo = new MedicationRequest(); + mo.setMedication(new Reference(medId)); + bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); + + ourLog.info("Request:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + + Bundle outcome = mySystemDao.transaction(mySrd, bundle); + ourLog.info("Response:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + /* + * Again! + */ + + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + + med = new Medication(); + medId = IdType.newRandomUuid(); + med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); + bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); + + mo = new MedicationRequest(); + mo.setMedication(new Reference(medId)); + bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); + + outcome = mySystemDao.transaction(mySrd, bundle); + + IdType medId2 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + IdType medOrderId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + assertTrue(medId1.isIdPartValidLong()); + assertTrue(medId2.isIdPartValidLong()); + assertTrue(medOrderId1.isIdPartValidLong()); + assertTrue(medOrderId2.isIdPartValidLong()); + + assertEquals(medId1, medId2); + assertNotEquals(medOrderId1, medOrderId2); + } + + // + // + // /** + // * Issue #55 + // */ + // @Test + // public void testTransactionWithCidIds() throws Exception { + // Bundle request = new Bundle(); + // + // Patient p1 = new Patient(); + // p1.setId("cid:patient1"); + // p1.addIdentifier().setSystem("system").setValue("testTransactionWithCidIds01"); + // res.add(p1); + // + // Observation o1 = new Observation(); + // o1.setId("cid:observation1"); + // o1.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds02"); + // o1.setSubject(new Reference("Patient/cid:patient1")); + // res.add(o1); + // + // Observation o2 = new Observation(); + // o2.setId("cid:observation2"); + // o2.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds03"); + // o2.setSubject(new Reference("Patient/cid:patient1")); + // res.add(o2); + // + // ourSystemDao.transaction(res); + // + // assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$")); + // assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$")); + // assertTrue(o2.getId().getValue(), o2.getId().getIdPart().matches("^[0-9]+$")); + // + // assertThat(o1.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + // assertThat(o2.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + // + // } + // + // @Test + // public void testTransactionWithDelete() throws Exception { + // Bundle request = new Bundle(); + // + // /* + // * Create 3 + // */ + // + // List res; + // res = new ArrayList(); + // + // Patient p1 = new Patient(); + // p1.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p1); + // + // Patient p2 = new Patient(); + // p2.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p2); + // + // Patient p3 = new Patient(); + // p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p3); + // + // ourSystemDao.transaction(res); + // + // /* + // * Verify + // */ + // + // IBundleProvider results = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", + // "testTransactionWithDelete")); + // assertEquals(3, results.size()); + // + // /* + // * Now delete 2 + // */ + // + // request = new Bundle(); + // res = new ArrayList(); + // List existing = results.getResources(0, 3); + // + // p1 = new Patient(); + // p1.setId(existing.get(0).getId()); + // ResourceMetadataKeyEnum.DELETED_AT.put(p1, InstantDt.withCurrentTime()); + // res.add(p1); + // + // p2 = new Patient(); + // p2.setId(existing.get(1).getId()); + // ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime()); + // res.add(p2); + // + // ourSystemDao.transaction(res); + // + // /* + // * Verify + // */ + // + // IBundleProvider results2 = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", + // "testTransactionWithDelete")); + // assertEquals(1, results2.size()); + // List existing2 = results2.getResources(0, 1); + // assertEquals(existing2.get(0).getId(), existing.get(2).getId()); + // + // } + + + @Test + public void testTransactionWithReferenceUuid() { + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.setActive(true); + p.setId(IdType.newRandomUuid()); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId()); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference(p.getId()); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue(); + assertThat(patientId, startsWith("Patient/")); + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("subject", new ReferenceParam(patientId)); + IBundleProvider found = myObservationDao.search(params); + assertEquals(1, found.size().intValue()); + } + + @Test + public void testTransactionWithRelativeOidIds() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Patient p1 = new Patient(); + p1.setId("urn:oid:0.1.2.3"); + p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); + res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + Observation o1 = new Observation(); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + o1.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Observation o2 = new Observation(); + o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); + o2.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Bundle resp = mySystemDao.transaction(mySrd, res); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(3, resp.getEntry().size()); + + assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + + o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); + o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); + assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + + } + + /** + * This is not the correct way to do it, but we'll allow it to be lenient + */ + @Test + public void testTransactionWithRelativeOidIdsQualified() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Patient p1 = new Patient(); + p1.setId("urn:oid:0.1.2.3"); + p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); + res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + Observation o1 = new Observation(); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + o1.setSubject(new Reference("Patient/urn:oid:0.1.2.3")); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Observation o2 = new Observation(); + o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); + o2.setSubject(new Reference("Patient/urn:oid:0.1.2.3")); + res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Bundle resp = mySystemDao.transaction(mySrd, res); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(3, resp.getEntry().size()); + + assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + + o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); + o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); + assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + + } + + /** + * See #467 + */ + @Test + public void testTransactionWithSelfReferentialLink() { + /* + * Link to each other + */ + Bundle request = new Bundle(); + + Organization o1 = new Organization(); + o1.setId(IdType.newRandomUuid()); + o1.setName("ORG1"); + request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST); + + Organization o2 = new Organization(); + o2.setName("ORG2"); + o2.setId(IdType.newRandomUuid()); + request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST); + + o1.getPartOf().setReference(o2.getId()); + o2.getPartOf().setReference(o1.getId()); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(2, resp.getEntry().size()); + + IdType id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + IdType id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + + ourLog.info("ID1: {}", id1); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); + IBundleProvider res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + /* + * Link to self + */ + request = new Bundle(); + + o1 = new Organization(); + o1.setId(id1); + o1.setName("ORG1"); + request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.PUT).setUrl(id1.toUnqualifiedVersionless().getValue()); + + o2 = new Organization(); + o2.setName("ORG2"); + o2.setId(id2); + request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.PUT).setUrl(id2.toUnqualifiedVersionless().getValue()); + + o1.getPartOf().setReference(o1.getId()); + o2.getPartOf().setReference(o2.getId()); + + resp = mySystemDao.transaction(mySrd, request); + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(2, resp.getEntry().size()); + + id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + + ourLog.info("ID1: {}", id1); + + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java new file mode 100644 index 00000000000..8ff334b3e69 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -0,0 +1,73 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import static org.junit.Assert.assertEquals; + +import java.util.*; + +import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.Observation; +import org.junit.*; + +import ca.uhn.fhir.context.*; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.util.TestUtil; + +public class SearchParamExtractorR4Test { + + private static FhirContext ourCtx = FhirContext.forR4(); + private static IValidationSupport ourValidationSupport; + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() { + ourValidationSupport = new DefaultProfileValidationSupport(); + } + + @Test + public void testParamWithOrInPath() { + Observation obs = new Observation(); + obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE"); + + ISearchParamRegistry searchParamRegistry = new ISearchParamRegistry() { + @Override + public Map getActiveSearchParams(String theResourceName) { + RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName); + Map sps = new HashMap(); + for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { + sps.put(nextSp.getName(), nextSp); + } + return sps; + } + + @Override + public void forceRefresh() { + // nothing + } + + @Override + public Map> getActiveSearchParams() { + throw new UnsupportedOperationException(); + } + + @Override + public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) { + throw new UnsupportedOperationException(); + } + }; + + SearchParamExtractorR4 extractor = new SearchParamExtractorR4(ourCtx, ourValidationSupport, searchParamRegistry); + Set tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs); + assertEquals(1, tokens.size()); + ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); + assertEquals("category", token.getParamName()); + assertEquals("SYSTEM", token.getSystem()); + assertEquals("CODE", token.getValue()); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java new file mode 100644 index 00000000000..758c6daf735 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java @@ -0,0 +1,302 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.rest.api.Constants; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.client.methods.*; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.auth.*; +import ca.uhn.fhir.util.TestUtil; + +public class AuthorizationInterceptorResourceProviderR4Test extends BaseResourceProviderR4Test { + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Override + public void before() throws Exception { + super.before(); + myDaoConfig.setAllowMultipleDelete(true); + unregisterInterceptors(); + } + + + private void unregisterInterceptors() { + for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { + if (next instanceof AuthorizationInterceptor) { + ourRestServer.unregisterInterceptor(next); + } + } + } + + /** + * See #503 + */ + @Test + public void testDeleteIsBlocked() { + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() + .build(); + } + }); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + IIdType id = ourClient.create().resource(patient).execute().getId(); + + try { + ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute(); + fail(); + } catch (ForbiddenOperationException e) { + // good + } + + patient = ourClient.read().resource(Patient.class).withId(id.toUnqualifiedVersionless()).execute(); + assertEquals(id.getValue(), patient.getId()); + } + + + /** + * See #503 + */ + @Test + public void testDeleteIsAllowedForCompartment() { + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + final IIdType id = ourClient.create().resource(patient).execute().getId(); + + Observation obsInCompartment = new Observation(); + obsInCompartment.setStatus(ObservationStatus.FINAL); + obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless()); + IIdType obsInCompartmentId = ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); + + Observation obsNotInCompartment = new Observation(); + obsNotInCompartment.setStatus(ObservationStatus.FINAL); + IIdType obsNotInCompartmentId = ourClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless(); + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() + .build(); + } + }); + + ourClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute(); + + try { + ourClient.delete().resourceById(obsNotInCompartmentId.toUnqualifiedVersionless()).execute(); + fail(); + } catch (ForbiddenOperationException e) { + // good + } + } + + @Test + public void testCreateConditional() { + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + final MethodOutcome output1 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + //@formatter:off + return new RuleBuilder() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen() + .allow().updateConditional().allResources() + .build(); + //@formatter:on + } + }); + + patient = new Patient(); + patient.setId(output1.getId().toUnqualifiedVersionless()); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + MethodOutcome output2 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart()); + + patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + try { + ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|101").execute(); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); + } + + patient = new Patient(); + patient.setId("999"); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + try { + ourClient.update().resource(patient).execute(); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); + } + + } + + /** + * See #667 + */ + @Test + public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { + Patient pt1 = new Patient(); + pt1.setActive(true); + final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.setActive(false); + final IIdType pid2 = ourClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless(); + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().write().allResources().inCompartment("Patient", pid1).andThen() + .build(); + } + }); + + Observation obs = new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid1)); + IIdType oid = ourClient.create().resource(obs).execute().getId().toUnqualified(); + + + unregisterInterceptors(); + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().write().allResources().inCompartment("Patient", pid2).andThen() + .build(); + } + }); + + /* + * Try to update to a new patient. The user has access to write to things in + * pid2's compartment, so this would normally be ok, but in this case they are overwriting + * a resource that is already in pid1's compartment, which shouldn't be allowed. + */ + obs = new Observation(); + obs.setId(oid); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid2)); + + try { + ourClient.update().resource(obs).execute(); + fail(); + } catch (ForbiddenOperationException e) { + // good + } + + } + + @Test + public void testDeleteResourceConditional() throws IOException { + String methodName = "testDeleteResourceConditional"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + final IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + pt = new Patient(); + pt.addName().setFamily("FOOFOOFOO"); + resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + response = ourHttpClient.execute(post); + final IdType id2; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id2 = new IdType(newIdString); + } finally { + response.close(); + } + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + //@formatter:off + return new RuleBuilder() + .allow("Rule 2").delete().allResources().inCompartment("Patient", new IdDt("Patient/" + id.getIdPart())).andThen() + .build(); + //@formatter:on + } + }); + + HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName); + response = ourHttpClient.execute(delete); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + } finally { + response.close(); + } + + delete = new HttpDelete(ourServerBase + "/Patient?name=FOOFOOFOO"); + response = ourHttpClient.execute(delete); + try { + assertEquals(403, response.getStatusLine().getStatusCode()); + } finally { + response.close(); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java new file mode 100644 index 00000000000..f76425a016e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -0,0 +1,198 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +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.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Patient; +import org.junit.*; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.*; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.DispatcherServlet; + +import ca.uhn.fhir.jpa.config.r4.WebsocketR4Config; +import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig; +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; +import ca.uhn.fhir.jpa.interceptor.r4.RestHookSubscriptionR4Interceptor; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; + +public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { + + protected static JpaValidationSupportChainR4 myValidationSupport; + protected static IGenericClient ourClient; + protected static CloseableHttpClient ourHttpClient; + protected static int ourPort; + protected static RestfulServer ourRestServer; + private static Server ourServer; + protected static String ourServerBase; + private static GenericWebApplicationContext ourWebApplicationContext; + private TerminologyUploaderProviderR4 myTerminologyUploaderProvider; + protected static SearchParamRegistryR4 ourSearchParamRegistry; + protected static DatabaseBackedPagingProvider ourPagingProvider; + protected static RestHookSubscriptionR4Interceptor ourRestHookSubscriptionInterceptor; + protected static ISearchDao mySearchEntityDao; + protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; + + public BaseResourceProviderR4Test() { + super(); + } + + @After + public void after() throws Exception { + myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Before + public void before() throws Exception { + myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + if (ourServer == null) { + ourPort = PortUtil.findFreePort(); + + ourRestServer = new RestfulServer(myFhirCtx); + + ourServerBase = "http://localhost:" + ourPort + "/fhir/context"; + + ourRestServer.setResourceProviders((List) myResourceProviders); + + ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); + + myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProviderR4.class); + + ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider); + + JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(ourRestServer, mySystemDao, myDaoConfig); + confProvider.setImplementationDescription("THIS IS THE DESC"); + ourRestServer.setServerConformanceProvider(confProvider); + + ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); + + Server server = new Server(ourPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(ourRestServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourWebApplicationContext = new GenericWebApplicationContext(); + ourWebApplicationContext.setParent(myAppCtx); + ourWebApplicationContext.refresh(); + // ContextLoaderListener loaderListener = new ContextLoaderListener(webApplicationContext); + // loaderListener.initWebApplicationContext(mock(ServletContext.class)); + // + proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext); + + DispatcherServlet dispatcherServlet = new DispatcherServlet(); + // dispatcherServlet.setApplicationContext(webApplicationContext); + dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class); + ServletHolder subsServletHolder = new ServletHolder(); + subsServletHolder.setServlet(dispatcherServlet); + subsServletHolder.setInitParameter( + ContextLoader.CONFIG_LOCATION_PARAM, + WebsocketR4Config.class.getName() + "\n" + + WebsocketR4DispatcherConfig.class.getName()); + proxyHandler.addServlet(subsServletHolder, "/*"); + + // Register a CORS filter + CorsConfiguration config = new CorsConfiguration(); + CorsInterceptor corsInterceptor = new CorsInterceptor(config); + config.addAllowedHeader("x-fhir-starter"); + config.addAllowedHeader("Origin"); + config.addAllowedHeader("Accept"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedHeader("Content-Type"); + config.addAllowedHeader("Access-Control-Request-Method"); + config.addAllowedHeader("Access-Control-Request-Headers"); + config.addAllowedOrigin("*"); + config.addExposedHeader("Location"); + config.addExposedHeader("Content-Location"); + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + ourRestServer.registerInterceptor(corsInterceptor); + + server.setHandler(proxyHandler); + server.start(); + + WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); + myValidationSupport = wac.getBean(JpaValidationSupportChainR4.class); + mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); + mySearchEntityDao = wac.getBean(ISearchDao.class); + ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionR4Interceptor.class); + ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class); + + myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); + ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase); + if (shouldLogClient()) { + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + builder.setMaxConnPerRoute(99); + ourHttpClient = builder.build(); + + ourServer = server; + } + + ourRestServer.setPagingProvider(ourPagingProvider); + } + + protected boolean shouldLogClient() { + return true; + } + + protected List toNameList(Bundle resp) { + List names = new ArrayList(); + for (BundleEntryComponent next : resp.getEntry()) { + Patient nextPt = (Patient) next.getResource(); + String nextStr = nextPt.getName().size() > 0 ? nextPt.getName().get(0).getGivenAsSingleString() + " " + nextPt.getName().get(0).getFamily() : ""; + if (isNotBlank(nextStr)) { + names.add(nextStr); + } + } + return names; + } + + @AfterClass + public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception { + ourServer.stop(); + ourHttpClient.close(); + ourServer = null; + ourHttpClient = null; + myValidationSupport.flush(); + myValidationSupport = null; + ourWebApplicationContext.close(); + ourWebApplicationContext = null; + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CorsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CorsR4Test.java new file mode 100644 index 00000000000..31571ce57c1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CorsR4Test.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.util.TestUtil; + +public class CorsR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CorsR4Test.class); + + @Test + public void saveLocalOrigin() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/Patient?name=test"); + get.addHeader("Origin", "file://"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + + ourLog.info(resp.toString()); + + IOUtils.closeQuietly(resp.getEntity().getContent()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} 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 new file mode 100644 index 00000000000..ba0db38b0e9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java @@ -0,0 +1,208 @@ +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.dao.DaoConfig; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.util.TestUtil; + +public class PatientEverythingR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PatientEverythingR4Test.class); + private String orgId; + private String patId; + private String encId1; + private String encId2; + private ArrayList myObsIds; + private String myWrongPatId; + private String myWrongEnc1; + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); + myDaoConfig.setEverythingIncludesFetchPageSize(new DaoConfig().getEverythingIncludesFetchPageSize()); + } + + @Override + public void before() throws Exception { + super.before(); + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + myDaoConfig.setAllowMultipleDelete(true); + + Organization org = new Organization(); + org.setName("an org"); + orgId = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless().getValue(); + ourLog.info("OrgId: {}", orgId); + + Patient patient = new Patient(); + patient.getManagingOrganization().setReference(orgId); + patId = ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless().getValue(); + + Patient patient2 = new Patient(); + patient2.getManagingOrganization().setReference(orgId); + myWrongPatId = ourClient.create().resource(patient2).execute().getId().toUnqualifiedVersionless().getValue(); + + Encounter enc1 = new Encounter(); + enc1.setStatus(EncounterStatus.CANCELLED); + enc1.getSubject().setReference(patId); + enc1.getServiceProvider().setReference(orgId); + encId1 = ourClient.create().resource(enc1).execute().getId().toUnqualifiedVersionless().getValue(); + + Encounter enc2 = new Encounter(); + enc2.setStatus(EncounterStatus.ARRIVED); + enc2.getSubject().setReference(patId); + enc2.getServiceProvider().setReference(orgId); + encId2 = ourClient.create().resource(enc2).execute().getId().toUnqualifiedVersionless().getValue(); + + Encounter wrongEnc1 = new Encounter(); + wrongEnc1.setStatus(EncounterStatus.ARRIVED); + wrongEnc1.getSubject().setReference(myWrongPatId); + wrongEnc1.getServiceProvider().setReference(orgId); + myWrongEnc1 = ourClient.create().resource(wrongEnc1).execute().getId().toUnqualifiedVersionless().getValue(); + + myObsIds = new ArrayList(); + for (int i = 0; i < 20; i++) { + Observation obs = new Observation(); + obs.getSubject().setReference(patId); + obs.setStatus(ObservationStatus.FINAL); + String obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless().getValue(); + myObsIds.add(obsId); + } + + } + + /** + * See #674 + */ + @Test + public void testEverythingReturnsCorrectResources() throws Exception { + + Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=100", EncodingEnum.JSON); + + assertNull(bundle.getLink("next")); + + Set actual = new TreeSet(); + for (BundleEntryComponent nextEntry : bundle.getEntry()) { + actual.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + + ourLog.info("Found IDs: {}", actual); + + assertThat(actual, hasItem(patId)); + assertThat(actual, hasItem(encId1)); + assertThat(actual, hasItem(encId2)); + assertThat(actual, hasItem(orgId)); + assertThat(actual, hasItems(myObsIds.toArray(new String[0]))); + assertThat(actual, not(hasItem(myWrongPatId))); + assertThat(actual, not(hasItem(myWrongEnc1))); + } + + /** + * See #674 + */ + @Test + public void testEverythingReturnsCorrectResourcesSmallPage() throws Exception { + myDaoConfig.setEverythingIncludesFetchPageSize(1); + + Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=100", EncodingEnum.JSON); + + assertNull(bundle.getLink("next")); + + Set actual = new TreeSet(); + for (BundleEntryComponent nextEntry : bundle.getEntry()) { + actual.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + + ourLog.info("Found IDs: {}", actual); + + assertThat(actual, hasItem(patId)); + assertThat(actual, hasItem(encId1)); + assertThat(actual, hasItem(encId2)); + assertThat(actual, hasItem(orgId)); + assertThat(actual, hasItems(myObsIds.toArray(new String[0]))); + assertThat(actual, not(hasItem(myWrongPatId))); + assertThat(actual, not(hasItem(myWrongEnc1))); + } + + /** + * See #674 + */ + @Test + public void testEverythingPagesWithCorrectEncodingJson() throws Exception { + + Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=1", EncodingEnum.JSON); + + assertNotNull(bundle.getLink("next").getUrl()); + assertThat(bundle.getLink("next").getUrl(), containsString("_format=json")); + bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.JSON); + + assertNotNull(bundle.getLink("next").getUrl()); + assertThat(bundle.getLink("next").getUrl(), containsString("_format=json")); + bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.JSON); + } + + /** + * See #674 + */ + @Test + public void testEverythingPagesWithCorrectEncodingXml() throws Exception { + + Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=xml&_count=1", EncodingEnum.XML); + + assertNotNull(bundle.getLink("next").getUrl()); + ourLog.info("Next link: {}", bundle.getLink("next").getUrl()); + assertThat(bundle.getLink("next").getUrl(), containsString("_format=xml")); + bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.XML); + + assertNotNull(bundle.getLink("next").getUrl()); + ourLog.info("Next link: {}", bundle.getLink("next").getUrl()); + assertThat(bundle.getLink("next").getUrl(), containsString("_format=xml")); + bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.XML); + } + + private Bundle fetchBundle(String theUrl, EncodingEnum theEncoding) throws IOException, ClientProtocolException { + Bundle bundle; + HttpGet get = new HttpGet(theUrl); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(theEncoding.getResourceContentTypeNonLegacy(), resp.getFirstHeader(ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE).getValue().replaceAll(";.*", "")); + bundle = theEncoding.newParser(myFhirCtx).parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8)); + } finally { + IOUtils.closeQuietly(resp); + } + + return bundle; + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/QuestionnaireResourceProviderR4.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/QuestionnaireResourceProviderR4.java new file mode 100644 index 00000000000..17408adbfb9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/QuestionnaireResourceProviderR4.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import org.hl7.fhir.r4.model.Questionnaire; +import org.hl7.fhir.instance.model.api.IBaseResource; + +public class QuestionnaireResourceProviderR4 extends JpaResourceProviderR4 { + + @Override + public Class getResourceType() { + return Questionnaire.class; + } + +} 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 new file mode 100644 index 00000000000..c1484e1e0d8 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java @@ -0,0 +1,428 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.*; + +import org.apache.commons.io.IOUtils; +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.CapabilityStatement.*; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.SearchParameter.XPathUsageType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.gclient.ReferenceClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProviderR4Test { + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); + } + + @Override + public void before() throws Exception { + super.before(); + } + + @Test + public void saveCreateSearchParamInvalidWithMissingStatus() throws IOException { + SearchParameter sp = new SearchParameter(); + sp.setCode("foo"); + sp.setExpression("Patient.gender"); + sp.setXpathUsage(XPathUsageType.NORMAL); + sp.setTitle("Foo Param"); + + try { + ourClient.create().resource(sp).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: SearchParameter.status is missing or invalid: null", e.getMessage()); + } + } + + @Test + public void testIncludeExtensionReferenceAsRecurse() throws Exception, IOException { + SearchParameter attendingSp = new SearchParameter(); + attendingSp.addBase("Patient"); + attendingSp.setCode("attending"); + attendingSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + attendingSp.setTitle("Attending"); + attendingSp.setExpression("Patient.extension('http://acme.org/attending')"); + attendingSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + attendingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + attendingSp.getTarget().add(new CodeType("Practitioner")); + IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless(); + + mySearchParamRegsitry.forceRefresh(); + + Practitioner p1 = new Practitioner(); + p1.addName().setFamily("P1"); + IIdType p1id = myPractitionerDao.create(p1).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().setFamily("P2"); + p2.addExtension().setUrl("http://acme.org/attending").setValue(new Reference(p1id)); + IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + Appointment app = new Appointment(); + app.addParticipant().getActor().setReference(p2id.getValue()); + IIdType appId = myAppointmentDao.create(app).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + HttpGet get = new HttpGet(ourServerBase + "/Appointment?_include:recurse=Appointment:patient&_include:recurse=Appointment:location&_include:recurse=Patient:attending&_pretty=true"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), Constants.CHARSET_UTF8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + + assertThat(resp, containsString(" foundResources = toUnqualifiedVersionlessIdValues(bundle); + assertThat(foundResources, contains(p1id.getValue())); + + } + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderCustomSearchParamR4Test.class); + + @Override + @Before + public void beforeResetConfig() { + super.beforeResetConfig(); + + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); + mySearchParamRegsitry.forceRefresh(); + } + + @Test + public void testConformanceOverrideAllowed() { + myDaoConfig.setDefaultSearchParamsCanBeOverridden(true); + + CapabilityStatement conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + Map map = extractSearchParams(conformance, "Patient"); + + CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + assertNull(param); + + param = map.get("gender"); + assertNotNull(param); + + TransactionTemplate txTemplate = newTxTemplate(); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + // Add a custom search parameter + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + } + }); + + // Disable an existing parameter + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("gender"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("Gender"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.RETIRED); + mySearchParameterDao.create(fooSp, mySrd); + } + }); + + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + mySearchParamRegsitry.forceRefresh(); + } + }); + + conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + map = extractSearchParams(conformance, "Patient"); + + param = map.get("foo"); + assertEquals("foo", param.getName()); + + param = map.get("gender"); + assertNull(param); + + } + + @Test + public void testConformanceOverrideNotAllowed() { + myDaoConfig.setDefaultSearchParamsCanBeOverridden(false); + + CapabilityStatement conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + Map map = extractSearchParams(conformance, "Patient"); + + CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + assertNull(param); + + param = map.get("gender"); + assertNotNull(param); + + // Add a custom search parameter + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + // Disable an existing parameter + fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("gender"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("Gender"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.RETIRED); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + map = extractSearchParams(conformance, "Patient"); + + param = map.get("foo"); + assertEquals("foo", param.getName()); + + param = map.get("gender"); + assertNotNull(param); + + } + + private Map extractSearchParams(CapabilityStatement conformance, String resType) { + Map map = new HashMap(); + for (CapabilityStatementRestComponent nextRest : conformance.getRest()) { + for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) { + if (!resType.equals(nextResource.getType())) { + continue; + } + for (CapabilityStatementRestResourceSearchParamComponent nextParam : nextResource.getSearchParam()) { + map.put(nextParam.getName(), nextParam); + } + } + } + return map; + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithCustomParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat2.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + Bundle result; + + result = ourClient + .search() + .forResource(Patient.class) + .where(new TokenClientParam("foo").exactly().code("male")) + .returnBundle(Bundle.class) + .execute(); + + foundResources = toUnqualifiedVersionlessIdValues(result); + assertThat(foundResources, contains(patId.getValue())); + + } + + @Test + public void testCreatingParamMarksCorrectResourcesForReindexing() { + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obsId = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + ResourceTable res = myResourceTableDao.findOne(patId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + res = myResourceTableDao.findOne(obsId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + res = myResourceTableDao.findOne(patId.getIdPartAsLong()); + assertEquals(null, res.getIndexStatus()); + res = myResourceTableDao.findOne(obsId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchQualifiedWithCustomReferenceParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.addBase("Patient"); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE); + fooSp.setTitle("FOO SP"); + fooSp.setExpression("Observation.subject"); + fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getSubject().setReferenceElement(patId); + IIdType obsId1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(org.hl7.fhir.r4.model.Observation.ObservationStatus.FINAL); + IIdType obsId2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + Bundle result; + + result = ourClient + .search() + .forResource(Observation.class) + .where(new ReferenceClientParam("foo").hasChainedProperty(Patient.GENDER.exactly().code("male"))) + .returnBundle(Bundle.class) + .execute(); + foundResources = toUnqualifiedVersionlessIdValues(result); + assertThat(foundResources, contains(obsId1.getValue())); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} 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 new file mode 100644 index 00000000000..ed5c4f6b85c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java @@ -0,0 +1,239 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.mockito.ArgumentCaptor; + +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.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.InterceptorAdapter; +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderInterceptorR4Test.class); + private IServerInterceptor myDaoInterceptor; + + private IServerInterceptor myServerInterceptor; + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.getInterceptors().remove(myDaoInterceptor); + ourRestServer.unregisterInterceptor(myServerInterceptor); + } + + @Override + public void before() throws Exception { + super.before(); + + myServerInterceptor = mock(IServerInterceptor.class); + myDaoInterceptor = mock(IServerInterceptor.class); + + resetServerInterceptor(); + + myDaoConfig.getInterceptors().add(myDaoInterceptor); + ourRestServer.registerInterceptor(myServerInterceptor); + + ourRestServer.registerInterceptor(new InterceptorAdapter() { + @Override + public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) { + super.incomingRequestPreHandled(theOperation, theProcessedRequest); + } + }); + + } + + private void resetServerInterceptor() throws ServletException, IOException { + reset(myServerInterceptor); + when(myServerInterceptor.handleException(any(RequestDetails.class), any(BaseServerResponseException.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myServerInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true); + when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + } + + @Test + public void testCreateResource() throws IOException { + String methodName = "testCreateResource"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response was: {}", resp); + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + } finally { + response.close(); + } + + ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myServerInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getValue()); + assertEquals("Patient", ardCaptor.getValue().getResourceType()); + assertNotNull(ardCaptor.getValue().getResource()); + + ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myDaoInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getValue()); + assertEquals("Patient", ardCaptor.getValue().getResourceType()); + assertNotNull(ardCaptor.getValue().getResource()); + } + + @Test + public void testCreateResourceWithVersionedReference() throws IOException, ServletException { + String methodName = "testCreateResourceWithVersionedReference"; + + Organization org = new Organization(); + org.setName("orgName"); + IIdType orgId = ourClient.create().resource(org).execute().getId().toUnqualified(); + assertNotNull(orgId.getVersionIdPartAsLong()); + + resetServerInterceptor(); + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + pt.setManagingOrganization(new Reference(orgId)); + + IParser parser = myFhirCtx.newXmlParser(); + parser.setDontStripVersionsFromReferencesAtPaths("Patient.managingOrganization"); + parser.setPrettyPrint(true); + String resource = parser.encodeResourceToString(pt); + + ourLog.info(resource); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response was: {}", resp); + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + } finally { + response.close(); + } + + ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myServerInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + + assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getValue()); + assertEquals("Patient", ardCaptor.getValue().getResourceType()); + assertNotNull(ardCaptor.getValue().getResource()); + + Patient patient; + patient = (Patient) ardCaptor.getAllValues().get(0).getResource(); + assertEquals(orgId.getValue(), patient.getManagingOrganization().getReference()); + + } + + @Test + public void testCreateResourceInTransaction() throws IOException { + String methodName = "testCreateResourceInTransaction"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + + Bundle bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + BundleEntryComponent entry = bundle.addEntry(); + entry.setFullUrl("Patient"); + entry.setResource(pt); + entry.getRequest().setMethod(HTTPVerb.POST); + entry.getRequest().setUrl("Patient"); + + String resource = myFhirCtx.newXmlParser().encodeResourceToString(bundle); + + HttpPost post = new HttpPost(ourServerBase + "/"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + } finally { + response.close(); + } + + /* + * Server Interceptor + */ + + ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); + assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType()); + assertNotNull(ardCaptor.getAllValues().get(0).getResource()); + assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1)); + assertEquals("Patient", ardCaptor.getAllValues().get(1).getResourceType()); + assertNotNull(ardCaptor.getAllValues().get(1).getResource()); + + ArgumentCaptor rdCaptor = ArgumentCaptor.forClass(RequestDetails.class); + ArgumentCaptor srCaptor = ArgumentCaptor.forClass(HttpServletRequest.class); + ArgumentCaptor sRespCaptor = ArgumentCaptor.forClass(HttpServletResponse.class); + verify(myServerInterceptor, times(1)).incomingRequestPostProcessed(rdCaptor.capture(), srCaptor.capture(), sRespCaptor.capture()); + + /* + * DAO Interceptor + */ + + ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myDaoInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); + assertEquals("Bundle", ardCaptor.getAllValues().get(0).getResourceType()); + assertNotNull(ardCaptor.getAllValues().get(0).getResource()); + assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1)); + assertEquals("Patient", ardCaptor.getAllValues().get(1).getResourceType()); + assertNotNull(ardCaptor.getAllValues().get(1).getResource()); + + rdCaptor = ArgumentCaptor.forClass(RequestDetails.class); + srCaptor = ArgumentCaptor.forClass(HttpServletRequest.class); + sRespCaptor = ArgumentCaptor.forClass(HttpServletResponse.class); + verify(myDaoInterceptor, times(0)).incomingRequestPostProcessed(rdCaptor.capture(), srCaptor.capture(), sRespCaptor.capture()); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderQuestionnaireResponseR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderQuestionnaireResponseR4Test.java new file mode 100644 index 00000000000..78423c8552b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderQuestionnaireResponseR4Test.java @@ -0,0 +1,235 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.*; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; +import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ResultSeverityEnum; + +public class ResourceProviderQuestionnaireResponseR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderQuestionnaireResponseR4Test.class); + private static RequestValidatingInterceptor ourValidatingInterceptor; + + @AfterClass + public static void afterClassClearContext() { + ourRestServer.unregisterInterceptor(ourValidatingInterceptor); + ourValidatingInterceptor = null; + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Override + @Before + public void before() throws Exception { + super.before(); + + if (ourValidatingInterceptor == null) { + ourValidatingInterceptor = new RequestValidatingInterceptor(); + ourValidatingInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + + Collection validators = myAppCtx.getBeansOfType(IValidatorModule.class).values(); + for (IValidatorModule next : validators) { + ourValidatingInterceptor.addValidatorModule(next); + } + ourRestServer.registerInterceptor(ourValidatingInterceptor); + } + + } + + + + @SuppressWarnings("unused") + @Test + public void testCreateWithLocalReference() { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); + + Questionnaire q1 = new Questionnaire(); + q1.addItem().setLinkId("link1").setType(QuestionnaireItemType.STRING); + IIdType qId = myQuestionnaireDao.create(q1, mySrd).getId().toUnqualifiedVersionless(); + + QuestionnaireResponse qr1 = new QuestionnaireResponse(); + qr1.getQuestionnaire().setReferenceElement(qId); + qr1.setStatus(QuestionnaireResponseStatus.COMPLETED); + qr1.addItem().setLinkId("link1").addAnswer().setValue(new DecimalType(123)); + try { + ourClient.create().resource(qr1).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.toString(), containsString("Answer value must be of type string")); + } + } + + @SuppressWarnings("unused") + @Test + public void testCreateWithAbsoluteReference() { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); + + Questionnaire q1 = new Questionnaire(); + q1.addItem().setLinkId("link1").setType(QuestionnaireItemType.STRING); + IIdType qId = myQuestionnaireDao.create(q1, mySrd).getId().toUnqualifiedVersionless(); + + QuestionnaireResponse qr1 = new QuestionnaireResponse(); + qr1.getQuestionnaire().setReferenceElement(qId.withServerBase("http://example.com", "Questionnaire")); + qr1.setStatus(QuestionnaireResponseStatus.COMPLETED); + qr1.addItem().setLinkId("link1").addAnswer().setValue(new DecimalType(123)); + try { + ourClient.create().resource(qr1).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.toString(), containsString("Answer value must be of type string")); + } + } + + @Test + public void testSaveQuestionnaire() throws Exception { + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse"); + post.setEntity(new StringEntity(input, ContentType.create(ca.uhn.fhir.rest.api.Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + final IdType id2; + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", responseString); + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(ca.uhn.fhir.rest.api.Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/QuestionnaireResponse/")); + id2 = new IdType(newIdString); + } finally { + IOUtils.closeQuietly(response); + } + + HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse/" + id2.getIdPart() + "?_format=xml&_pretty=true"); + response = ourHttpClient.execute(get); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", responseString); + assertThat(responseString, containsString("Exclusion Criteria")); + } finally { + IOUtils.closeQuietly(response); + } + + + + } + + @Test + public void testValidateOnNoId() throws Exception { + HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse/$validate"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", responseString); + assertThat(responseString, containsString("No resource supplied for $validate operation")); + assertEquals(400, response.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(response); + } + + } + + + /** + * From a Skype message from Brian Postlethwaite + */ + @Test + public void testValidateQuestionnaireResponseWithNoIdForCreate() throws Exception { + + String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"
!-- populated from the rendered HTML below -->
\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\"}}]}"; + HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true"); + post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON)); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", responseString); + assertEquals(200, response.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(response); + } + + } + + /** + * From a Skype message from Brian Postlethwaite + */ + @Test + public void testValidateQuestionnaireResponseWithNoIdForUpdate() throws Exception { + + String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"update\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"
!-- populated from the rendered HTML below -->
\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\"}}]}"; + HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true"); + post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON)); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", responseString); + assertThat(responseString, containsString("Resource has no ID")); + assertEquals(422, response.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(response); + } + + } + + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java new file mode 100644 index 00000000000..075ed457670 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.junit.Assert.*; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderR4BundleTest extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4BundleTest.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + /** + * See #401 + */ + @Test + public void testBundlePreservesFullUrl() throws Exception { + + Bundle bundle = new Bundle(); + bundle.setType(BundleType.DOCUMENT); + + Composition composition = new Composition(); + composition.setTitle("Visit Summary"); + bundle.addEntry().setFullUrl("http://foo").setResource(composition); + + IIdType id = ourClient.create().resource(bundle).execute().getId(); + + Bundle retBundle = ourClient.read().resource(Bundle.class).withId(id).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(retBundle)); + + assertEquals("http://foo", bundle.getEntry().get(0).getFullUrl()); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java new file mode 100644 index 00000000000..9b04ff87ed2 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java @@ -0,0 +1,264 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CodeSystemTest.class); + private IIdType myExtensionalVsId; + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Before + @Transactional + public void before02() throws IOException { + CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + myCodeSystemDao.create(cs, mySrd); + + ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); + } + + @Test + public void testLookupOnExternalCode() { + ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermSvc, mySrd); + + Parameters respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + + // With HTTP GET + respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .useHttpGet() + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + + } + + @Test + public void testLookupOperationByCodeAndSystemBuiltInCode() { + Parameters respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSN")) + .andParameter("system", new UriType("http://hl7.org/fhir/v2/0203")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals("Accession ID", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + } + + @Test + public void testLookupOperationByCodeAndSystemBuiltInNonexistantCode() { + //@formatter:off + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSNAAAAAA")) + .andParameter("system", new UriType("http://hl7.org/fhir/v2/0203")) + .execute(); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + //@formatter:on + } + + @Test + public void testLookupOperationByCodeAndSystemUserDefinedCode() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + } + + @Test + public void testLookupOperationByCodeAndSystemUserDefinedNonExistantCode() { + //@formatter:off + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9AAAAA")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + //@formatter:on + } + + @Test + public void testLookupOperationByCoding() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9")) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + } + + @Test + public void testLookupOperationByInvalidCombination() { + //@formatter:off + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9")) + .andParameter("code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: $lookup can only validate (system AND code) OR (coding.system AND coding.code)", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testLookupOperationByInvalidCombination2() { + //@formatter:off + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: $lookup can only validate (system AND code) OR (coding.system AND coding.code)", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testLookupOperationByInvalidCombination3() { + //@formatter:off + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode(null)) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: No code, coding, or codeableConcept provided to validate", e.getMessage()); + } + //@formatter:on + } + + @Test +// @Ignore + public void testLookupOperationForBuiltInCode() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("M")) + .andParameter("system", new UriType("http://hl7.org/fhir/v3/MaritalStatus")) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("Unknown", ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(1).getName()); + assertEquals("Married", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(2).getName()); + assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).booleanValue()); + } + + +} 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 new file mode 100644 index 00000000000..15ee2d2f7db --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -0,0 +1,4225 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.hamcrest.Matchers.*; +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.*; +import java.math.BigDecimal; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import ca.uhn.fhir.rest.api.Constants; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.http.NameValuePair; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.*; +import org.apache.http.entity.*; +import org.apache.http.message.BasicNameValuePair; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.Encounter.EncounterLocationComponent; +import org.hl7.fhir.r4.model.Encounter.EncounterStatus; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.*; +import org.junit.*; +import org.springframework.test.util.AopTestUtils; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; + +import com.google.common.collect.Lists; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.primitive.InstantDt; +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.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.IGenericClient; +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.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; + +public class ResourceProviderR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4Test.class); + private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw; + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false); + + } + + @Override + public void before() throws Exception { + super.before(); + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + myDaoConfig.setAllowMultipleDelete(true); + } + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc); + } + + private void checkParamMissing(String paramName) throws IOException, ClientProtocolException { + HttpGet get = new HttpGet(ourServerBase + "/Observation?" + paramName + ":missing=false"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + IOUtils.closeQuietly(resp.getEntity().getContent()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + } + + + private ArrayList genResourcesOfType(Bundle theRes, Class theClass) { + ArrayList retVal = new ArrayList(); + for (BundleEntryComponent next : theRes.getEntry()) { + if (next.getResource() != null) { + if (theClass.isAssignableFrom(next.getResource().getClass())) { + retVal.add(next.getResource()); + } + } + } + return retVal; + } + + /** + * See #484 + */ + @Test + public void saveAndRetrieveBasicResource() throws IOException { + String input = IOUtils.toString(getClass().getResourceAsStream("/basic-stu3.xml"), StandardCharsets.UTF_8); + + String respString = ourClient.transaction().withBundle(input).prettyPrint().execute(); + ourLog.info(respString); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); + IdType id = new IdType(bundle.getEntry().get(0).getResponse().getLocation()); + + Basic basic = ourClient.read().resource(Basic.class).withId(id).execute(); + List exts = basic.getExtensionsByUrl("http://localhost:1080/hapi-fhir-jpaserver-example/baseDstu2/StructureDefinition/DateID"); + assertEquals(1, exts.size()); + } + + private List searchAndReturnUnqualifiedIdValues(String uri) throws IOException, ClientProtocolException { + List ids; + HttpGet get = new HttpGet(uri); + + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp); + ids = toUnqualifiedIdValues(bundle); + } finally { + IOUtils.closeQuietly(response); + } + return ids; + } + + private List searchAndReturnUnqualifiedVersionlessIdValues(String uri) throws IOException, ClientProtocolException { + List ids; + HttpGet get = new HttpGet(uri); + + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp); + ids = toUnqualifiedVersionlessIdValues(bundle); + } finally { + IOUtils.closeQuietly(response); + } + return ids; + } + + // Y + @Test + public void testBundleCreate() throws Exception { + IGenericClient client = ourClient; + + String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/document-father-r4.json"), StandardCharsets.UTF_8); + IIdType id = client.create().resource(resBody).execute().getId(); + + ourLog.info("Created: {}", id); + + Bundle bundle = client.read().resource(Bundle.class).withId(id).execute(); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + } + + @Test + public void testBundleCreateWithTypeTransaction() throws Exception { + IGenericClient client = ourClient; + + String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/document-father-r4.json"), StandardCharsets.UTF_8); + resBody = resBody.replace("\"type\": \"document\"", "\"type\": \"transaction\""); + try { + client.create().resource(resBody).execute().getId(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction")); + } + } + + @Test + public void testCodeSearch() { + Subscription subs = new Subscription(); + subs.setStatus(SubscriptionStatus.ACTIVE); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?"); + IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless(); + + //@formatter:off + Bundle resp = ourClient + .search() + .forResource(Subscription.class) + .where(Subscription.TYPE.exactly().code(SubscriptionChannelType.WEBSOCKET.toCode())) + .and(Subscription.STATUS.exactly().code(SubscriptionStatus.ACTIVE.toCode())) + .returnBundle(Bundle.class) + .execute(); + //@formatter:off + + assertThat(toUnqualifiedVersionlessIds(resp), contains(id)); + + //@formatter:off + resp = ourClient + .search() + .forResource(Subscription.class) + .where(Subscription.TYPE.exactly().systemAndCode(SubscriptionChannelType.WEBSOCKET.getSystem(), SubscriptionChannelType.WEBSOCKET.toCode())) + .and(Subscription.STATUS.exactly().systemAndCode(SubscriptionStatus.ACTIVE.getSystem(), SubscriptionStatus.ACTIVE.toCode())) + .returnBundle(Bundle.class) + .execute(); + //@formatter:off + + assertThat(toUnqualifiedVersionlessIds(resp), contains(id)); + + //@formatter:off + resp = ourClient + .search() + .forResource(Subscription.class) + .where(Subscription.TYPE.exactly().systemAndCode(SubscriptionChannelType.WEBSOCKET.getSystem(), SubscriptionChannelType.WEBSOCKET.toCode())) + .and(Subscription.STATUS.exactly().systemAndCode("foo", SubscriptionStatus.ACTIVE.toCode())) + .returnBundle(Bundle.class) + .execute(); + //@formatter:off + + assertThat(toUnqualifiedVersionlessIds(resp), empty()); + + } + + @Test + public void testCountParam() throws Exception { + List resources = new ArrayList(); + for (int i = 0; i < 100; i++) { + Organization org = new Organization(); + org.setName("rpdstu2_testCountParam_01"); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + Bundle found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).count(10).returnBundle(Bundle.class).execute(); + assertEquals(100, found.getTotal()); + assertEquals(10, found.getEntry().size()); + + found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).count(999).returnBundle(Bundle.class).execute(); + assertEquals(100, found.getTotal()); + assertEquals(50, found.getEntry().size()); + + } + + /** + * See #438 + */ + @Test + public void testCreateAndUpdateBinary() throws ClientProtocolException, Exception { + byte[] arr = { 1, 21, 74, 123, -44 }; + Binary binary = new Binary(); + binary.setContent(arr); + binary.setContentType("dansk"); + + IIdType resource = ourClient.create().resource(binary).execute().getId(); + + Binary fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); + assertEquals("1", fromDB.getIdElement().getVersionIdPart()); + + arr[0] = 2; + HttpPut putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); + putRequest.setEntity(new ByteArrayEntity(arr, ContentType.parse("dansk"))); + CloseableHttpResponse resp = ourHttpClient.execute(putRequest); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertEquals(resource.withVersion("2").getValue(), resp.getFirstHeader("Location").getValue()); + } finally { + IOUtils.closeQuietly(resp); + } + + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); + assertEquals("2", fromDB.getIdElement().getVersionIdPart()); + + arr[0] = 3; + fromDB.setContent(arr); + String encoded = myFhirCtx.newJsonParser().encodeResourceToString(fromDB); + putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); + putRequest.setEntity(new StringEntity(encoded, ContentType.parse("application/json+fhir"))); + resp = ourHttpClient.execute(putRequest); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertEquals(resource.withVersion("3").getValue(), resp.getFirstHeader("Location").getValue()); + } finally { + IOUtils.closeQuietly(resp); + } + + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); + assertEquals("3", fromDB.getIdElement().getVersionIdPart()); + + // Now an update with the wrong ID in the body + + arr[0] = 4; + binary.setId(""); + encoded = myFhirCtx.newJsonParser().encodeResourceToString(binary); + putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); + putRequest.setEntity(new StringEntity(encoded, ContentType.parse("application/json+fhir"))); + resp = ourHttpClient.execute(putRequest); + try { + assertEquals(400, resp.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(resp); + } + + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); + assertEquals("3", fromDB.getIdElement().getVersionIdPart()); + + } + + /** + * Issue submitted by Bryn + */ + @Test + public void testCreateBundle() throws IOException { + String input = IOUtils.toString(getClass().getResourceAsStream("/bryn-bundle.json"), StandardCharsets.UTF_8); + Validate.notNull(input); + ourClient.create().resource(input).execute().getResource(); + } + + @Test + public void testCreateConditional() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + + MethodOutcome output1 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + + MethodOutcome output2 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart()); + } + + @Test + public void testCreateIncludesRequestValidatorInterceptorOutcome() throws IOException { + RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); + assertTrue(interceptor.isAddValidationResultsToResponseOperationOutcome()); + interceptor.setFailOnSeverity(null); + + ourRestServer.registerInterceptor(interceptor); + try { + // Missing status, which is mandatory + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:foo").setValue("bar"); + IBaseResource outcome = ourClient.create().resource(obs).execute().getOperationOutcome(); + + String encodedOo = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(encodedOo); + assertThat(encodedOo, containsString("cvc-complex-type.2.4.b")); + assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/")); + + interceptor.setAddValidationResultsToResponseOperationOutcome(false); + outcome = ourClient.create().resource(obs).execute().getOperationOutcome(); + encodedOo = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(encodedOo); + assertThat(encodedOo, not(containsString("cvc-complex-type.2.4.b"))); + assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/")); + + } finally { + ourRestServer.unregisterInterceptor(interceptor); + } + } + + @Test + @Ignore + public void testCreateQuestionnaireResponseWithValidation() throws IOException { + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://urn/system"); + cs.addConcept().setCode("code0"); + ourClient.create().resource(cs).execute(); + + ValueSet options = new ValueSet(); + options.getCompose().addInclude().setSystem("http://urn/system"); + IIdType optId = ourClient.create().resource(options).execute().getId(); + + Questionnaire q = new Questionnaire(); + q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setOptions(new Reference(optId)); + IIdType qId = ourClient.create().resource(q).execute().getId(); + + QuestionnaireResponse qa; + + // Good code + + qa = new QuestionnaireResponse(); + qa.getQuestionnaire().setReference(qId.toUnqualifiedVersionless().getValue()); + qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0")); + ourClient.create().resource(qa).execute(); + + // Bad code + + qa = new QuestionnaireResponse(); + qa.getQuestionnaire().setReference(qId.toUnqualifiedVersionless().getValue()); + qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1")); + try { + ourClient.create().resource(qa).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Question with linkId[link0]")); + } + } + + @Test + public void testCreateResourceConditional() throws IOException { + String methodName = "testCreateResourceConditional"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?name=" + methodName); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?name=" + methodName); + response = ourHttpClient.execute(post); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertEquals(id.getValue(), newIdString); // version should match for conditional create + } finally { + response.close(); + } + + } + + + + @Test + public void testCreateResourceConditionalComplex() throws IOException { + Patient pt = new Patient(); + pt.addIdentifier().setSystem("http://general-hospital.co.uk/Identifiers").setValue("09832345234543876876"); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=http://general-hospital.co.uk/Identifiers|09832345234543876876"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + IdType id; + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + IdType id2; + response = ourHttpClient.execute(post); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id2 = new IdType(newIdString); + } finally { + response.close(); + } + +// //@formatter:off +// IIdType id3 = ourClient +// .update() +// .resource(pt) +// .conditionalByUrl("Patient?identifier=http://general-hospital.co.uk/Identifiers|09832345234543876876") +// .execute().getId(); +// //@formatter:on + + assertEquals(id.getValue(), id2.getValue()); + } + + @Test + public void testCreateResourceReturnsOperationOutcomeByDefault() throws IOException { + String resource = ""; + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(response.toString()); + ourLog.info(respString); + assertThat(respString, containsString("")); + } finally { + response.getEntity().getContent().close(); + response.close(); + } + } + + + @Test + public void testCreateResourceWithNumericId() throws IOException { + String resource = ""; + + HttpPost post = new HttpPost(ourServerBase + "/Patient/2"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertEquals(400, response.getStatusLine().getStatusCode()); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString); + assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", + oo.getIssue().get(0).getDiagnostics()); + + } finally { + response.getEntity().getContent().close(); + response.close(); + } + } + + @Test + public void testCreateWithForcedId() throws IOException { + String methodName = "testCreateWithForcedId"; + + Patient p = new Patient(); + p.addName().setFamily(methodName); + p.setId(methodName); + + IIdType optId = ourClient.update().resource(p).execute().getId(); + assertEquals(methodName, optId.getIdPart()); + assertEquals("1", optId.getVersionIdPart()); + } + + @Test + public void testDeepChaining() { + Location l1 = new Location(); + l1.getNameElement().setValue("testDeepChainingL1"); + IIdType l1id = ourClient.create().resource(l1).execute().getId(); + + Location l2 = new Location(); + l2.getNameElement().setValue("testDeepChainingL2"); + l2.getPartOf().setReferenceElement(l1id.toVersionless().toUnqualified()); + IIdType l2id = ourClient.create().resource(l2).execute().getId(); + + Encounter e1 = new Encounter(); + e1.addIdentifier().setSystem("urn:foo").setValue("testDeepChainingE1"); + e1.getStatusElement().setValue(EncounterStatus.INPROGRESS); + EncounterLocationComponent location = e1.addLocation(); + location.getLocation().setReferenceElement(l2id.toUnqualifiedVersionless()); + location.setPeriod(new Period().setStart(new Date(), TemporalPrecisionEnum.SECOND).setEnd(new Date(), TemporalPrecisionEnum.SECOND)); + IIdType e1id = ourClient.create().resource(e1).execute().getId(); + + //@formatter:off + Bundle res = ourClient.search() + .forResource(Encounter.class) + .where(Encounter.IDENTIFIER.exactly().systemAndCode("urn:foo", "testDeepChainingE1")) + .include(Encounter.INCLUDE_LOCATION.asRecursive()) + .include(Location.INCLUDE_PARTOF.asRecursive()) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(3, res.getEntry().size()); + assertEquals(1, genResourcesOfType(res, Encounter.class).size()); + assertEquals(e1id.toUnqualifiedVersionless(), genResourcesOfType(res, Encounter.class).get(0).getIdElement().toUnqualifiedVersionless()); + + } + + @Test + public void testDeleteConditionalMultiple() { + String methodName = "testDeleteConditionalMultiple"; + + myDaoConfig.setAllowMultipleDelete(false); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("FAM1"); + IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("FAM2"); + IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + try { + //@formatter:off + ourClient + .delete() + .resourceConditionalByType(Patient.class) + .where(Patient.IDENTIFIER.exactly().code(methodName)) + .execute(); + //@formatter:on + fail(); + } catch (PreconditionFailedException e) { + assertEquals("HTTP 412 Precondition Failed: Failed to DELETE resource with match URL \"Patient?identifier=testDeleteConditionalMultiple\" because this search matched 2 resources", + e.getMessage()); + } + + // Not deleted yet.. + ourClient.read().resource("Patient").withId(id1).execute(); + ourClient.read().resource("Patient").withId(id2).execute(); + + myDaoConfig.setAllowMultipleDelete(true); + + //@formatter:off + IBaseOperationOutcome response = ourClient + .delete() + .resourceConditionalByType(Patient.class) + .where(Patient.IDENTIFIER.exactly().code(methodName)) + .execute(); + //@formatter:on + + String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response); + ourLog.info(encoded); + assertThat(encoded, containsString( + "")); + } finally { + IOUtils.closeQuietly(resp); + } + + } + + @Test + public void testDeleteInvalidReference() throws IOException { + HttpDelete delete = new HttpDelete(ourServerBase + "/Patient"); + CloseableHttpResponse response = ourHttpClient.execute(delete); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertEquals(400, response.getStatusLine().getStatusCode()); + assertThat(responseString, containsString("Can not perform delete, no ID provided")); + } finally { + response.close(); + } + } + + /** + * Test for #345 + */ + @Test + public void testDeleteNormal() { + Patient p = new Patient(); + p.addName().setFamily("FAM"); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + ourClient.read().resource(Patient.class).withId(id).execute(); + + ourClient.delete().resourceById(id).execute(); + + try { + ourClient.read().resource(Patient.class).withId(id).execute(); + fail(); + } catch (ResourceGoneException e) { + // good + } + } + + @Test + public void testDeleteResourceConditional1() throws IOException { + String methodName = "testDeleteResourceConditional1"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName); + response = ourHttpClient.execute(delete); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, resp); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + } finally { + response.close(); + } + + HttpGet read = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart()); + response = ourHttpClient.execute(read); + try { + ourLog.info(response.toString()); + assertEquals(Constants.STATUS_HTTP_410_GONE, response.getStatusLine().getStatusCode()); + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, resp); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Resource was deleted at")); + } finally { + response.close(); + } + + // Delete should now have no matches + + delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName); + response = ourHttpClient.execute(delete); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, resp); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Deletion failed.")); + } finally { + response.close(); + } + + } + + /** + * Based on email from Rene Spronk + */ + @Test + public void testDeleteResourceConditional2() throws IOException, Exception { + String methodName = "testDeleteResourceConditional2"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + pt.addIdentifier().setSystem("http://ghh.org/patient").setValue(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + /* + * Try it with a raw socket call. The Apache client won't let us use the unescaped "|" in the URL but we want to make sure that works too.. + */ + Socket sock = new Socket(); + sock.setSoTimeout(3000); + try { + sock.connect(new InetSocketAddress("localhost", ourPort)); + sock.getOutputStream().write(("DELETE /fhir/context/Patient?identifier=http://ghh.org/patient|" + methodName + " HTTP/1.1\n").getBytes("UTF-8")); + sock.getOutputStream().write("Host: localhost\n".getBytes("UTF-8")); + sock.getOutputStream().write("\n".getBytes("UTF-8")); + + BufferedReader socketInput = new BufferedReader(new InputStreamReader(sock.getInputStream())); + + // String response = ""; + StringBuilder b = new StringBuilder(); + char[] buf = new char[1000]; + while (socketInput.read(buf) != -1) { + b.append(buf); + } + String resp = b.toString(); + + ourLog.info("Resp: {}", resp); + } catch (SocketTimeoutException e) { + e.printStackTrace(); + } finally { + sock.close(); + } + + Thread.sleep(1000); + + HttpGet read = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart()); + response = ourHttpClient.execute(read); + try { + ourLog.info(response.toString()); + assertEquals(Constants.STATUS_HTTP_410_GONE, response.getStatusLine().getStatusCode()); + } finally { + response.close(); + } + + } + + @Test + public void testDeleteReturnsOperationOutcome() { + Patient p = new Patient(); + p.addName().setFamily("FAM"); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + IBaseOperationOutcome resp = ourClient.delete().resourceById(id).execute(); + OperationOutcome oo = (OperationOutcome) resp; + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + } + + /** + * See issue #52 + */ + @Test + public void testDocumentManifestResources() throws Exception { + myFhirCtx.getResourceDefinition(Practitioner.class); + myFhirCtx.getResourceDefinition(DocumentManifest.class); + + IGenericClient client = ourClient; + + int initialSize = client.search().forResource(DocumentManifest.class).returnBundle(Bundle.class).execute().getEntry().size(); + + String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/documentmanifest.json"), StandardCharsets.UTF_8); + client.create().resource(resBody).execute(); + + int newSize = client.search().forResource(DocumentManifest.class).returnBundle(Bundle.class).execute().getEntry().size(); + + assertEquals(1, newSize - initialSize); + + } + + /** + * See issue #52 + */ + @Test + public void testDocumentReferenceResources() throws Exception { + IGenericClient client = ourClient; + + int initialSize = client.search().forResource(DocumentReference.class).returnBundle(Bundle.class).execute().getEntry().size(); + + String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/documentreference.json"), StandardCharsets.UTF_8); + client.create().resource(resBody).execute(); + + int newSize = client.search().forResource(DocumentReference.class).returnBundle(Bundle.class).execute().getEntry().size(); + + assertEquals(1, newSize - initialSize); + + } + + @Test + public void testEmptySearch() throws Exception { + Bundle responseBundle; + + responseBundle = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); + assertEquals(0, responseBundle.getTotal()); + + responseBundle = ourClient.search().forResource(Patient.class).where(Patient.NAME.matches().value("AAA")).returnBundle(Bundle.class).execute(); + assertEquals(0, responseBundle.getTotal()); + + responseBundle = ourClient.search().forResource(Patient.class).where(new StringClientParam("_content").matches().value("AAA")).returnBundle(Bundle.class).execute(); + assertEquals(0, responseBundle.getTotal()); + + } + + @Test + public void testEverythingEncounterInstance() throws Exception { + String methodName = "testEverythingEncounterInstance"; + + Organization org1parent = new Organization(); + org1parent.setId("org1parent"); + org1parent.setName(methodName + "1parent"); + IIdType orgId1parent = ourClient.update().resource(org1parent).execute().getId().toUnqualifiedVersionless(); + + Organization org1 = new Organization(); + org1.setName(methodName + "1"); + org1.getPartOf().setReferenceElement(orgId1parent); + IIdType orgId1 = ourClient.create().resource(org1).execute().getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addName().setFamily(methodName); + p.getManagingOrganization().setReferenceElement(orgId1); + IIdType patientId = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Organization org2 = new Organization(); + org2.setName(methodName + "1"); + IIdType orgId2 = ourClient.create().resource(org2).execute().getId().toUnqualifiedVersionless(); + + Device dev = new Device(); + dev.setModel(methodName); + dev.getOwner().setReferenceElement(orgId2); + IIdType devId = ourClient.create().resource(dev).execute().getId().toUnqualifiedVersionless(); + + Location locParent = new Location(); + locParent.setName(methodName + "Parent"); + IIdType locPId = ourClient.create().resource(locParent).execute().getId().toUnqualifiedVersionless(); + + Location locChild = new Location(); + locChild.setName(methodName); + locChild.getPartOf().setReferenceElement(locPId); + IIdType locCId = ourClient.create().resource(locChild).execute().getId().toUnqualifiedVersionless(); + + Encounter encU = new Encounter(); + encU.getSubject().setReferenceElement(patientId); + encU.addLocation().getLocation().setReferenceElement(locCId); + IIdType encUId = ourClient.create().resource(encU).execute().getId().toUnqualifiedVersionless(); + + Encounter enc = new Encounter(); + enc.getSubject().setReferenceElement(patientId); + enc.addLocation().getLocation().setReferenceElement(locCId); + IIdType encId = ourClient.create().resource(enc).execute().getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(patientId); + obs.getDevice().setReferenceElement(devId); + obs.getContext().setReferenceElement(encId); + IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless(); + + ourLog.info("IDs: EncU:" + encUId.getIdPart() + " Enc:" + encId.getIdPart() + " " + patientId.toUnqualifiedVersionless()); + + Parameters output = ourClient.operation().onInstance(encId).named("everything").withNoParameters(Parameters.class).execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + List ids = toUnqualifiedVersionlessIds(b); + assertThat(ids, containsInAnyOrder(patientId, encId, orgId1, orgId2, orgId1parent, locPId, locCId, obsId, devId)); + assertThat(ids, not(containsInRelativeOrder(encUId))); + + ourLog.info(ids.toString()); + } + + @Test + public void testEverythingEncounterType() throws Exception { + String methodName = "testEverythingEncounterInstance"; + + Organization org1parent = new Organization(); + org1parent.setId("org1parent"); + org1parent.setName(methodName + "1parent"); + IIdType orgId1parent = ourClient.update().resource(org1parent).execute().getId().toUnqualifiedVersionless(); + + Organization org1 = new Organization(); + org1.setName(methodName + "1"); + org1.getPartOf().setReferenceElement(orgId1parent); + IIdType orgId1 = ourClient.create().resource(org1).execute().getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addName().setFamily(methodName); + p.getManagingOrganization().setReferenceElement(orgId1); + IIdType patientId = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Organization org2 = new Organization(); + org2.setName(methodName + "1"); + IIdType orgId2 = ourClient.create().resource(org2).execute().getId().toUnqualifiedVersionless(); + + Device dev = new Device(); + dev.setModel(methodName); + dev.getOwner().setReferenceElement(orgId2); + IIdType devId = ourClient.create().resource(dev).execute().getId().toUnqualifiedVersionless(); + + Location locParent = new Location(); + locParent.setName(methodName + "Parent"); + IIdType locPId = ourClient.create().resource(locParent).execute().getId().toUnqualifiedVersionless(); + + Location locChild = new Location(); + locChild.setName(methodName); + locChild.getPartOf().setReferenceElement(locPId); + IIdType locCId = ourClient.create().resource(locChild).execute().getId().toUnqualifiedVersionless(); + + Encounter encU = new Encounter(); + encU.addIdentifier().setValue(methodName); + IIdType encUId = ourClient.create().resource(encU).execute().getId().toUnqualifiedVersionless(); + + Encounter enc = new Encounter(); + enc.getSubject().setReferenceElement(patientId); + enc.addLocation().getLocation().setReferenceElement(locCId); + IIdType encId = ourClient.create().resource(enc).execute().getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(patientId); + obs.getDevice().setReferenceElement(devId); + obs.getContext().setReferenceElement(encId); + IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless(); + + Parameters output = ourClient.operation().onType(Encounter.class).named("everything").withNoParameters(Parameters.class).execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + List ids = toUnqualifiedVersionlessIds(b); + assertThat(ids, containsInAnyOrder(patientId, encUId, encId, orgId1, orgId2, orgId1parent, locPId, locCId, obsId, devId)); + + ourLog.info(ids.toString()); + } + + @Test + public void testEverythingInstanceWithContentFilter() { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.addName().setFamily("Everything").addGiven("Arthur"); + IIdType ptId2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); + + Device dev1 = new Device(); + dev1.setManufacturer("Some Manufacturer"); + IIdType devId1 = myDeviceDao.create(dev1, mySrd).getId().toUnqualifiedVersionless(); + + Device dev2 = new Device(); + dev2.setManufacturer("Some Manufacturer 2"); + myDeviceDao.create(dev2, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getText().setDivAsString("
OBSTEXT1
"); + obs1.getSubject().setReferenceElement(ptId1); + obs1.getCode().addCoding().setCode("CODE1"); + obs1.setValue(new StringType("obsvalue1")); + obs1.getDevice().setReferenceElement(devId1); + IIdType obsId1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getSubject().setReferenceElement(ptId1); + obs2.getCode().addCoding().setCode("CODE2"); + obs2.setValue(new StringType("obsvalue2")); + IIdType obsId2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs3 = new Observation(); + obs3.getSubject().setReferenceElement(ptId2); + obs3.getCode().addCoding().setCode("CODE3"); + obs3.setValue(new StringType("obsvalue3")); + IIdType obsId3 = myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); + + List actual; + StringAndListParam param; + + ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] { ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart() }); + + param = new StringAndListParam(); + param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); + + //@formatter:off + Parameters response = ourClient + .operation() + .onInstance(ptId1) + .named("everything") + .withParameter(Parameters.class, Constants.PARAM_CONTENT, new StringType("obsvalue1")) + .execute(); + //@formatter:on + + actual = toUnqualifiedVersionlessIds((Bundle) response.getParameter().get(0).getResource()); + assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1)); + + } + + /** + * See #147"Patient" + */ + @Test + public void testEverythingPatientDoesntRepeatPatient() throws Exception { + Bundle b; + IParser parser = myFhirCtx.newJsonParser(); + b = parser.parseResource(Bundle.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/bug147-bundle-r4.json"))); + + Bundle resp = ourClient.transaction().withBundle(b).execute(); + List ids = new ArrayList(); + for (BundleEntryComponent next : resp.getEntry()) { + IdType toAdd = new IdType(next.getResponse().getLocation()).toUnqualifiedVersionless(); + ids.add(toAdd); + } + ourLog.info("Created: " + ids.toString()); + + IdType patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals("Patient", patientId.getResourceType()); + + { + Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute(); + b = (Bundle) output.getParameter().get(0).getResource(); + + ids = new ArrayList(); + boolean dupes = false; + for (BundleEntryComponent next : b.getEntry()) { + IdType toAdd = next.getResource().getIdElement().toUnqualifiedVersionless(); + dupes = dupes | ids.contains(toAdd); + ids.add(toAdd); + } + ourLog.info("$everything: " + ids.toString()); + + assertFalse(ids.toString(), dupes); + } + + /* + * Now try with a size specified + */ + { + Parameters input = new Parameters(); + input.addParameter().setName(Constants.PARAM_COUNT).setValue(new UnsignedIntType(100)); + Parameters output = ourClient.operation().onInstance(patientId).named("everything").withParameters(input).execute(); + b = (Bundle) output.getParameter().get(0).getResource(); + + ids = new ArrayList(); + boolean dupes = false; + for (BundleEntryComponent next : b.getEntry()) { + IdType toAdd = next.getResource().getIdElement().toUnqualifiedVersionless(); + dupes = dupes | ids.contains(toAdd); + ids.add(toAdd); + } + ourLog.info("$everything: " + ids.toString()); + + assertFalse(ids.toString(), dupes); + assertThat(ids.toString(), containsString("Condition")); + assertThat(ids.size(), greaterThan(10)); + } + } + + /** + * Test for #226 + */ + @Test + public void testEverythingPatientIncludesBackReferences() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Medication med = new Medication(); + med.getCode().setText(methodName); + IIdType medId = myMedicationDao.create(med, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat = new Patient(); + pat.addAddress().addLine(methodName); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + MedicationRequest mo = new MedicationRequest(); + mo.getSubject().setReferenceElement(patId); + mo.setMedication(new Reference(medId)); + IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); + + Parameters output = ourClient.operation().onInstance(patId).named("everything").withNoParameters(Parameters.class).execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + List ids = toUnqualifiedVersionlessIds(b); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(patId, medId, moId)); + } + + /** + * See #148 + */ + @Test + public void testEverythingPatientIncludesCondition() throws Exception { + Bundle b = new Bundle(); + Patient p = new Patient(); + p.setId("1"); + b.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST); + + Condition c = new Condition(); + c.getSubject().setReference("Patient/1"); + b.addEntry().setResource(c).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = ourClient.transaction().withBundle(b).execute(); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp)); + + IdType patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals("Patient", patientId.getResourceType()); + + Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute(); + b = (Bundle) output.getParameter().get(0).getResource(); + + List ids = new ArrayList(); + for (BundleEntryComponent next : b.getEntry()) { + IdType toAdd = next.getResource().getIdElement().toUnqualifiedVersionless(); + ids.add(toAdd); + } + + assertThat(ids.toString(), containsString("Patient/")); + assertThat(ids.toString(), containsString("Condition/")); + + } + + @Test + public void testEverythingPatientOperation() throws Exception { + String methodName = "testEverythingOperation"; + + Organization org1parent = new Organization(); + org1parent.setId("org1parent"); + org1parent.setName(methodName + "1parent"); + IIdType orgId1parent = ourClient.update().resource(org1parent).execute().getId().toUnqualifiedVersionless(); + + Organization org1 = new Organization(); + org1.setName(methodName + "1"); + org1.getPartOf().setReferenceElement(orgId1parent); + IIdType orgId1 = ourClient.create().resource(org1).execute().getId().toUnqualifiedVersionless(); + + Patient p = new Patient(); + p.addName().setFamily(methodName); + p.getManagingOrganization().setReferenceElement(orgId1); + IIdType patientId = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Organization org2 = new Organization(); + org2.setName(methodName + "1"); + IIdType orgId2 = ourClient.create().resource(org2).execute().getId().toUnqualifiedVersionless(); + + Device dev = new Device(); + dev.setModel(methodName); + dev.getOwner().setReferenceElement(orgId2); + IIdType devId = ourClient.create().resource(dev).execute().getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(patientId); + obs.getDevice().setReferenceElement(devId); + IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless(); + + Encounter enc = new Encounter(); + enc.getSubject().setReferenceElement(patientId); + IIdType encId = ourClient.create().resource(enc).execute().getId().toUnqualifiedVersionless(); + + Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + List ids = toUnqualifiedVersionlessIds(b); + assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2, orgId1parent)); + + ourLog.info(ids.toString()); + } + + @Test + public void testEverythingPatientType() throws Exception { + String methodName = "testEverythingPatientType"; + + Organization o1 = new Organization(); + o1.setName(methodName + "1"); + IIdType o1Id = ourClient.create().resource(o1).execute().getId().toUnqualifiedVersionless(); + Organization o2 = new Organization(); + o2.setName(methodName + "2"); + IIdType o2Id = ourClient.create().resource(o2).execute().getId().toUnqualifiedVersionless(); + + Patient p1 = new Patient(); + p1.addName().setFamily(methodName + "1"); + p1.getManagingOrganization().setReferenceElement(o1Id); + IIdType p1Id = ourClient.create().resource(p1).execute().getId().toUnqualifiedVersionless(); + Patient p2 = new Patient(); + p2.addName().setFamily(methodName + "2"); + p2.getManagingOrganization().setReferenceElement(o2Id); + IIdType p2Id = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless(); + + Condition c1 = new Condition(); + c1.getSubject().setReferenceElement(p1Id); + IIdType c1Id = ourClient.create().resource(c1).execute().getId().toUnqualifiedVersionless(); + Condition c2 = new Condition(); + c2.getSubject().setReferenceElement(p2Id); + IIdType c2Id = ourClient.create().resource(c2).execute().getId().toUnqualifiedVersionless(); + + Condition c3 = new Condition(); + c3.addIdentifier().setValue(methodName + "3"); + IIdType c3Id = ourClient.create().resource(c3).execute().getId().toUnqualifiedVersionless(); + + Parameters output = ourClient.operation().onType(Patient.class).named("everything").withNoParameters(Parameters.class).execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + + assertEquals(BundleType.SEARCHSET, b.getType()); + List ids = toUnqualifiedVersionlessIds(b); + + assertThat(ids, containsInAnyOrder(o1Id, o2Id, p1Id, p2Id, c1Id, c2Id)); + assertThat(ids, not(containsInRelativeOrder(c3Id))); + } + + // retest + @Test + public void testEverythingPatientWithLastUpdatedAndSort() throws Exception { + String methodName = "testEverythingWithLastUpdatedAndSort"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType oId = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless(); + + long time1 = System.currentTimeMillis(); + Thread.sleep(10); + + Patient p = new Patient(); + p.addName().setFamily(methodName); + p.getManagingOrganization().setReferenceElement(oId); + IIdType pId = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + long time2 = System.currentTimeMillis(); + Thread.sleep(10); + + Condition c = new Condition(); + c.getCode().setText(methodName); + c.getSubject().setReferenceElement(pId); + IIdType cId = ourClient.create().resource(c).execute().getId().toUnqualifiedVersionless(); + + Thread.sleep(10); + long time3 = System.currentTimeMillis(); + + // %3E=> %3C=< + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_lastUpdated=%3E" + new InstantType(new Date(time1)).getValueAsString()); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pId, cId)); + } finally { + response.close(); + } + + get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_lastUpdated=%3E" + new InstantType(new Date(time2)).getValueAsString() + "&_lastUpdated=%3C" + + new InstantType(new Date(time3)).getValueAsString()); + response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pId, cId)); + } finally { + response.close(); + } + + /* + * Sorting is not working since the performance enhancements in 2.4 but + * sorting for lastupdated is non-standard anyhow.. Hopefully at some point + * we can bring this back + */ + // get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_lastUpdated=%3E" + new InstantType(new Date(time1)).getValueAsString() + "&_sort=_lastUpdated"); + // response = ourHttpClient.execute(get); + // try { + // assertEquals(200, response.getStatusLine().getStatusCode()); + // String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + // IOUtils.closeQuietly(response.getEntity().getContent()); + // ourLog.info(output); + // List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + // ourLog.info(ids.toString()); + // assertThat(ids, contains(pId, cId)); + // } finally { + // response.close(); + // } + // + // get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_sort:desc=_lastUpdated"); + // response = ourHttpClient.execute(get); + // try { + // assertEquals(200, response.getStatusLine().getStatusCode()); + // String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + // IOUtils.closeQuietly(response.getEntity().getContent()); + // ourLog.info(output); + // List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + // ourLog.info(ids.toString()); + // assertThat(ids, contains(cId, pId, oId)); + // } finally { + // response.close(); + // } + + } + + /** + * Per message from David Hay on Skype + */ + @Test + public void testEverythingWithLargeSet() throws Exception { + + String inputString = IOUtils.toString(getClass().getResourceAsStream("/david_big_bundle.json"), StandardCharsets.UTF_8); + Bundle inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputString); + inputBundle.setType(BundleType.TRANSACTION); + + assertEquals(53, inputBundle.getEntry().size()); + + Set allIds = new TreeSet(); + for (BundleEntryComponent nextEntry : inputBundle.getEntry()) { + nextEntry.getRequest().setMethod(HTTPVerb.PUT); + nextEntry.getRequest().setUrl(nextEntry.getResource().getId()); + allIds.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + + assertEquals(53, allIds.size()); + + mySystemDao.transaction(mySrd, inputBundle); + + Bundle responseBundle = ourClient + .operation() + .onInstance(new IdType("Patient/A161443")) + .named("everything") + .withParameter(Parameters.class, "_count", new IntegerType(20)) + .useHttpGet() + .returnResourceType(Bundle.class) + .execute(); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(responseBundle)); + + List ids = new ArrayList(); + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + ids.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + Collections.sort(ids); + ourLog.info("{} ids: {}", ids.size(), ids); + + assertThat(responseBundle.getEntry().size(), lessThanOrEqualTo(25)); + + TreeSet idsSet = new TreeSet(); + for (int i = 0; i < responseBundle.getEntry().size(); i++) { + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + } + + String nextUrl = responseBundle.getLink("next").getUrl(); + responseBundle = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + for (int i = 0; i < responseBundle.getEntry().size(); i++) { + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + } + + nextUrl = responseBundle.getLink("next").getUrl(); + responseBundle = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + for (int i = 0; i < responseBundle.getEntry().size(); i++) { + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + } + + assertEquals(null, responseBundle.getLink("next")); + + assertThat(idsSet, hasItem("List/A161444")); + assertThat(idsSet, hasItem("List/A161468")); + assertThat(idsSet, hasItem("List/A161500")); + + ourLog.info("Expected {} - {}", allIds.size(), allIds); + ourLog.info("Actual {} - {}", idsSet.size(), idsSet); + assertEquals(allIds, idsSet); + + } + + /** + * Per message from David Hay on Skype + */ + @Test + public void testEverythingWithLargeSet2() throws Exception { + Patient p = new Patient(); + p.setActive(true); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + for (int i = 1; i < 77; i++) { + Observation obs = new Observation(); + obs.setId("A" + StringUtils.leftPad(Integer.toString(i), 2, '0')); + obs.setSubject(new Reference(id)); + ourClient.update().resource(obs).execute(); + } + + Bundle responseBundle = ourClient.operation().onInstance(id).named("everything").withParameter(Parameters.class, "_count", new IntegerType(50)).useHttpGet().returnResourceType(Bundle.class) + .execute(); + + TreeSet ids = new TreeSet(); + for (int i = 0; i < responseBundle.getEntry().size(); i++) { + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + ids.add(nextEntry.getResource().getIdElement().getIdPart()); + } + } + + ourLog.info("Have {} IDs: {}", ids.size(), ids); + + BundleLinkComponent nextLink = responseBundle.getLink("next"); + while (nextLink != null) { + String nextUrl = nextLink.getUrl(); + responseBundle = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + for (int i = 0; i < responseBundle.getEntry().size(); i++) { + for (BundleEntryComponent nextEntry : responseBundle.getEntry()) { + ids.add(nextEntry.getResource().getIdElement().getIdPart()); + } + } + + ourLog.info("Have {} IDs: {}", ids.size(), ids); + nextLink = responseBundle.getLink("next"); + } + + assertThat(ids, hasItem(id.getIdPart())); + for (int i = 1; i < 77; i++) { + assertThat(ids, hasItem("A" + StringUtils.leftPad(Integer.toString(i), 2, '0'))); + } + assertEquals(77, ids.size()); + } + + @Test + public void testEverythingWithOnlyPatient() { + Patient p = new Patient(); + p.setActive(true); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000); + + Bundle response = ourClient + .operation() + .onInstance(id) + .named("everything") + .withNoParameters(Parameters.class) + .returnResourceType(Bundle.class) + .execute(); + + assertEquals(1, response.getEntry().size()); + } + + // private void delete(String theResourceType, String theParamName, String theParamValue) { + // Bundle resources; + // do { + // IQuery forResource = ourClient.search().forResource(theResourceType); + // if (theParamName != null) { + // forResource = forResource.where(new StringClientParam(theParamName).matches().value(theParamValue)); + // } + // resources = forResource.execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } while (resources.size() > 0); + // } + // + // private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) + // { + // Bundle resources = ourClient.search().forResource(theResourceType).where(new + // TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } + + @SuppressWarnings("unused") + @Test + public void testFullTextSearch() throws RuntimeException, Exception { + Observation obs1 = new Observation(); + obs1.getCode().setText("Systolic Blood Pressure"); + obs1.setStatus(ObservationStatus.FINAL); + obs1.setValue(new Quantity(123)); + obs1.setComment("obs1"); + IIdType id1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.getCode().setText("Diastolic Blood Pressure"); + obs2.setStatus(ObservationStatus.FINAL); + obs2.setValue(new Quantity(81)); + IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/Observation?_content=systolic&_pretty=true"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertThat(responseString, containsString(id1.getIdPart())); + } finally { + response.close(); + } + } + + @Test + public void testGetResourceCountsOperation() throws Exception { + String methodName = "testMetaOperations"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/$get-resource-counts"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + assertThat(output, containsString(" ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); + assertThat(ids, contains(pid0.getValue())); + } + + @Test + public void testHasParameterNoResults() throws Exception { + + HttpGet get = new HttpGet(ourServerBase + "/AllergyIntolerance?_has=Provenance:target:userID=12345"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertThat(resp, containsString("Invalid _has parameter syntax: _has")); + } finally { + IOUtils.closeQuietly(response); + } + + } + + @Test + public void testHistoryWithAtParameter() throws Exception { + String methodName = "testHistoryWithFromAndTo"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + List preDates = Lists.newArrayList(); + List ids = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + preDates.add(new Date()); + Thread.sleep(100); + patient.setId(id); + patient.getName().get(0).getFamilyElement().setValue(methodName + "_i" + i); + ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue()); + } + + List idValues; + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient/" + id.getIdPart() + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); + assertThat(idValues.toString(), idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); + assertThat(idValues.toString(), idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); + assertThat(idValues.toString(), idValues, contains(ids.get(2), ids.get(1), ids.get(0))); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt2060"); + assertThat(idValues.toString(), idValues, empty()); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=" + InstantDt.withCurrentTime().getYear()); + assertThat(idValues.toString(), idValues, hasSize(10)); // 10 is the page size + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=ge" + InstantDt.withCurrentTime().getYear()); + assertThat(idValues.toString(), idValues, hasSize(10)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt" + InstantDt.withCurrentTime().getYear()); + assertThat(idValues, hasSize(0)); + } + + @Test + public void testHistoryWithDeletedResource() { + String methodName = "testHistoryWithDeletedResource"; + + Patient patient = new Patient(); + patient.addName().setFamily(methodName); + IIdType id = ourClient.create().resource(patient).execute().getId().toVersionless(); + ourClient.delete().resourceById(id).execute(); + patient.setId(id); + ourClient.update().resource(patient).execute(); + + Bundle history = ourClient.history().onInstance(id).andReturnBundle(Bundle.class).prettyPrint().summaryMode(SummaryEnum.DATA).execute(); + assertEquals(3, history.getEntry().size()); + assertEquals(id.withVersion("3").getValue(), history.getEntry().get(0).getResource().getId()); + assertEquals(1, ((Patient) history.getEntry().get(0).getResource()).getName().size()); + + assertEquals(id.withVersion("2").getValue(), history.getEntry().get(1).getResource().getId()); + assertEquals(HTTPVerb.DELETE, history.getEntry().get(1).getRequest().getMethodElement().getValue()); + assertEquals("http://localhost:" + ourPort + "/fhir/context/Patient/" + id.getIdPart() + "/_history/2", history.getEntry().get(1).getRequest().getUrl()); + assertEquals(0, ((Patient) history.getEntry().get(1).getResource()).getName().size()); + + assertEquals(id.withVersion("1").getValue(), history.getEntry().get(2).getResource().getId()); + assertEquals(1, ((Patient) history.getEntry().get(2).getResource()).getName().size()); + + try { + myBundleDao.validate(history, null, null, null, null, null, mySrd); + } catch (PreconditionFailedException e) { + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + throw e; + } + } + + @Test + public void testIdAndVersionInBodyForCreate() throws IOException { + String methodName = "testIdAndVersionInBodyForCreate"; + + Patient pt = new Patient(); + pt.setId("Patient/AAA/_history/4"); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt); + + ourLog.info("Input: {}", resource); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", respString); + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + assertEquals("1", id.getVersionIdPart()); + assertNotEquals("AAA", id.getIdPart()); + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart()); + response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", respString); + assertThat(respString, containsString("")); + assertThat(respString, containsString("")); + } finally { + response.close(); + } + } + + @Test + public void testIdAndVersionInBodyForUpdate() throws IOException { + String methodName = "testIdAndVersionInBodyForUpdate"; + + Patient pt = new Patient(); + pt.setId("Patient/AAA/_history/4"); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt); + + ourLog.info("Input: {}", resource); + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", respString); + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + assertEquals("1", id.getVersionIdPart()); + assertNotEquals("AAA", id.getIdPart()); + + HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart() + "/_history/1"); + put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + response = ourHttpClient.execute(put); + try { + String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info("Response: {}", respString); + assertEquals(400, response.getStatusLine().getStatusCode()); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, respString); + assertEquals( + "Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"AAA\" does not match URL ID of \"" + + id.getIdPart() + "\"", + oo.getIssue().get(0).getDiagnostics()); + } finally { + response.close(); + } + + } + + /** + * See issue #52 + */ + @Test + public void testImagingStudyResources() throws Exception { + IGenericClient client = ourClient; + + int initialSize = client.search().forResource(ImagingStudy.class).returnBundle(Bundle.class).execute().getEntry().size(); + + String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/imagingstudy.json"), StandardCharsets.UTF_8); + client.create().resource(resBody).execute(); + + int newSize = client.search().forResource(ImagingStudy.class).returnBundle(Bundle.class).execute().getEntry().size(); + + assertEquals(1, newSize - initialSize); + + } + + @Test + public void testIncludeWithExternalReferences() { + myDaoConfig.setAllowExternalReferences(true); + + Patient p = new Patient(); + p.getManagingOrganization().setReference("http://example.com/Organization/123"); + ourClient.create().resource(p).execute(); + + Bundle b = ourClient.search().forResource("Patient").include(Patient.INCLUDE_ORGANIZATION).returnBundle(Bundle.class).execute(); + assertEquals(1, b.getEntry().size()); + } + + @Test + public void testMetadata() throws Exception { + HttpGet get = new HttpGet(ourServerBase + "/metadata"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, stringContainsInOrder("THIS IS THE DESC")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + @SuppressWarnings("unused") + @Test + public void testMetadataSuperParamsAreIncluded() throws IOException { + StructureDefinition p = new StructureDefinition(); + p.setAbstract(true); + p.setUrl("http://example.com/foo"); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Bundle resp = ourClient + .search() + .forResource(StructureDefinition.class) + .where(StructureDefinition.URL.matches().value("http://example.com/foo")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, resp.getTotal()); + } + + @Test + public void testMetaOperations() throws Exception { + String methodName = "testMetaOperations"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + IIdType id = ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); + + Meta meta = ourClient.meta().get(Meta.class).fromResource(id).execute(); + assertEquals(0, meta.getTag().size()); + + Meta inMeta = new Meta(); + inMeta.addTag().setSystem("urn:system1").setCode("urn:code1"); + meta = ourClient.meta().add().onResource(id).meta(inMeta).execute(); + assertEquals(1, meta.getTag().size()); + + inMeta = new Meta(); + inMeta.addTag().setSystem("urn:system1").setCode("urn:code1"); + meta = ourClient.meta().delete().onResource(id).meta(inMeta).execute(); + assertEquals(0, meta.getTag().size()); + + } + + @Test + public void testMetaOperationWithNoMetaParameter() throws Exception { + Patient p = new Patient(); + p.addName().setFamily("testMetaAddInvalid"); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + //@formatter:off + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + //@formatter:on + + HttpPost post = new HttpPost(ourServerBase + "/Patient/" + id.getIdPart() + "/$meta-add"); + post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertEquals(400, response.getStatusLine().getStatusCode()); + assertThat(output, containsString("Input contains no parameter with name 'meta'")); + } finally { + response.close(); + } + + post = new HttpPost(ourServerBase + "/Patient/" + id.getIdPart() + "/$meta-delete"); + post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + response = ourHttpClient.execute(post); + try { + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertEquals(400, response.getStatusLine().getStatusCode()); + assertThat(output, containsString("Input contains no parameter with name 'meta'")); + } finally { + response.close(); + } + + } + + @Test + public void testPagingOverEverythingSet() throws InterruptedException { + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + Bundle response = ourClient + .operation() + .onInstance(new IdType(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(10, response.getEntry().size()); + if (response.getTotalElement().getValueAsString() != null) { + assertEquals("21", response.getTotalElement().getValueAsString()); + } + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 2 + + String nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + + assertEquals(10, response.getEntry().size()); + if (response.getTotalElement().getValueAsString() != null) { + assertEquals("21", response.getTotalElement().getValueAsString()); + } + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 3 + Thread.sleep(2000); + + nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + + assertEquals(1, response.getEntry().size()); + assertEquals("21", response.getTotalElement().getValueAsString()); + assertEquals(null, response.getLink("next")); + + } + + @Test + public void testPagingOverEverythingSetWithNoPagingProvider() throws InterruptedException { + ourRestServer.setPagingProvider(null); + + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + Bundle response = ourClient + .operation() + .onInstance(new IdType(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(21, response.getEntry().size()); + assertEquals(21, response.getTotalElement().getValue().intValue()); + assertEquals(null, response.getLink("next")); + + } + + @Test + public void testPatchUsingJsonPatch() throws Exception { + String methodName = "testPatchUsingJsonPatch"; + IIdType pid1; + { + Patient patient = new Patient(); + patient.setActive(true); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily(methodName).addGiven("Joe"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); + patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); + + CloseableHttpResponse response = ourHttpClient.execute(patch); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + assertThat(responseString, containsString("")); + } finally { + response.close(); + } + + Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); + assertEquals("1", newPt.getIdElement().getVersionIdPart()); + assertEquals(true, newPt.getActive()); + } + + @Test + public void testPatchUsingJsonPatchWithContentionCheckGood() throws Exception { + String methodName = "testPatchUsingJsonPatchWithContentionCheckGood"; + IIdType pid1; + { + Patient patient = new Patient(); + patient.setActive(true); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily(methodName).addGiven("Joe"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); + patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); + patch.addHeader("If-Match", "W/\"1\""); + + CloseableHttpResponse response = ourHttpClient.execute(patch); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + assertThat(responseString, containsString("false"; + patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); + + CloseableHttpResponse response = ourHttpClient.execute(patch); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + assertThat(responseString, containsString("HELLO WORLD"); + p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveExistingNarrative01"); + + IIdType newId = ourClient.create().resource(p1).encodedJson().execute().getId(); + + Patient actual = ourClient.read().resource(Patient.class).withId(newId).encodedJson().execute(); + assertEquals("
HELLO WORLD
", actual.getText().getDiv().getValueAsString()); + } + + @Test + public void testSaveAndRetrieveExistingNarrativeXml() { + Patient p1 = new Patient(); + p1.getText().setStatus(NarrativeStatus.GENERATED); + p1.getText().getDiv().setValueAsString("
HELLO WORLD
"); + p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveExistingNarrative01"); + + IIdType newId = ourClient.create().resource(p1).encodedXml().execute().getId(); + + Patient actual = ourClient.read().resource(Patient.class).withId(newId).encodedXml().execute(); + assertEquals("
HELLO WORLD
", actual.getText().getDiv().getValueAsString()); + } + + @Test + public void testSaveAndRetrieveResourceWithExtension() { + Patient nextPatient = new Patient(); + nextPatient.setId("Patient/B"); + nextPatient + .addExtension() + .setUrl("http://foo") + .setValue(new Reference("Practitioner/A")); + + ourClient.update().resource(nextPatient).execute(); + + Patient p = ourClient.read().resource(Patient.class).withId("B").execute(); + + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); + ourLog.info(encoded); + + assertThat(encoded, containsString("http://foo")); + } + + @Test + public void testSaveAndRetrieveWithContained() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSaveAndRetrieveWithContained01"); + + Organization o1 = new Organization(); + o1.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSaveAndRetrieveWithContained02"); + + p1.getManagingOrganization().setResource(o1); + + IIdType newId = ourClient.create().resource(p1).execute().getId(); + + Patient actual = ourClient.read(Patient.class, new UriDt(newId.getValue())); + assertEquals(1, actual.getContained().size()); + + //@formatter:off + Bundle b = ourClient + .search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system:rpdstu2", "testSaveAndRetrieveWithContained01")) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals(1, b.getEntry().size()); + + } + + @Test + public void testSaveAndRetrieveWithoutNarrative() { + Patient p1 = new Patient(); + p1.getText().setDivAsString("
IdentifiertestSearchByResourceChain01
"); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); + + IdType newId = (IdType) ourClient.create().resource(p1).execute().getId(); + + Patient actual = ourClient.read(Patient.class, newId.getIdPart()); + assertThat(actual.getText().getDiv().getValueAsString(), containsString("IdentifiertestSearchByResourceChain01")); + } + + @Test + public void testSearchBundleDoesntIncludeTextElement() throws Exception { + HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json"); + CloseableHttpResponse response = ourHttpClient.execute(read); + try { + String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(text); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode()); + assertThat(text, not(containsString("\"text\",\"type\""))); + } finally { + response.close(); + } + } + + @Test + public void testSearchByExtendedChars() throws Exception { + for (int i = 0; i < 10; i++) { + Patient p = new Patient(); + p.addName().setFamily("Jernelöv"); + p.addIdentifier().setValue("ID" + i); + myPatientDao.create(p, mySrd); + } + + String uri = ourServerBase + "/Patient?name=" + URLEncoder.encode("Jernelöv", "UTF-8") + "&_count=5&_pretty=true"; + ourLog.info("URI: {}", uri); + HttpGet get = new HttpGet(uri); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String output = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + + Bundle b = myFhirCtx.newXmlParser().parseResource(Bundle.class, output); + + assertEquals("http://localhost:" + ourPort + "/fhir/context/Patient?_count=5&_pretty=true&name=Jernel%C3%B6v", b.getLink("self").getUrl()); + + Patient p = (Patient) b.getEntry().get(0).getResource(); + assertEquals("Jernelöv", p.getName().get(0).getFamily()); + + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + + } + + @Test + public void testSearchByIdentifier() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01"); + p1.addName().setFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01"); + IdType p1Id = (IdType) ourClient.create().resource(p1).execute().getId(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02"); + p2.addName().setFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02"); + ourClient.create().resource(p2).execute().getId(); + + //@formatter:off + Bundle actual = ourClient + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(1, actual.getEntry().size()); + assertEquals(ourServerBase + "/Patient/" + p1Id.getIdPart(), actual.getEntry().get(0).getFullUrl()); + assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + assertEquals(SearchEntryMode.MATCH, actual.getEntry().get(0).getSearch().getModeElement().getValue()); + } + + @Test + public void testSearchByIdentifierWithoutSystem() { + + Patient p1 = new Patient(); + p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01"); + IdType p1Id = (IdType) ourClient.create().resource(p1).execute().getId(); + + //@formatter:off + Bundle actual = ourClient + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals(1, actual.getEntry().size()); + assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + + } + + @Test + public void testSearchByIdOr() { + IIdType id1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + Bundle found; + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().systemAndValues(null, id1.getIdPart(), id2.getIdPart())) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().systemAndValues(null, Arrays.asList(id1.getIdPart(), id2.getIdPart(), "FOOOOO"))) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().systemAndCode(null, id1.getIdPart())) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().codes(id1.getIdPart(), id2.getIdPart())) + .and(BaseResource.RES_ID.exactly().code(id1.getIdPart())) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().codes(Arrays.asList(id1.getIdPart(), id2.getIdPart(), "FOOOOO"))) + .and(BaseResource.RES_ID.exactly().code(id1.getIdPart())) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().codes(id1.getIdPart(), id2.getIdPart(), "FOOO")) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().codes("FOOO")) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), empty()); + + } + + @Test + public void testSearchByLastUpdated() throws Exception { + String methodName = "testSearchByLastUpdated"; + + Patient p = new Patient(); + p.addName().setFamily(methodName+"1"); + IIdType pid1 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Thread.sleep(10); + long time1 = System.currentTimeMillis(); + Thread.sleep(10); + + Patient p2 = new Patient(); + p2.addName().setFamily(methodName+"2"); + IIdType pid2 = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=lt" + new InstantType(new Date(time1)).getValueAsString()); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pid1)); + } finally { + response.close(); + } + + get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=gt" + new InstantType(new Date(time1)).getValueAsString()); + response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pid2)); + } finally { + response.close(); + } + + } + + @Test + public void testSearchByReferenceIds() { + Organization o1 = new Organization(); + o1.setName("testSearchByResourceChainName01"); + IIdType o1id = ourClient.create().resource(o1).execute().getId().toUnqualifiedVersionless(); + Organization o2 = new Organization(); + o2.setName("testSearchByResourceChainName02"); + IIdType o2id = ourClient.create().resource(o2).execute().getId().toUnqualifiedVersionless(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByReferenceIds01"); + p1.addName().setFamily("testSearchByReferenceIdsFamily01").addGiven("testSearchByReferenceIdsGiven01"); + p1.setManagingOrganization(new Reference(o1id.toUnqualifiedVersionless())); + IIdType p1Id = ourClient.create().resource(p1).execute().getId(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testSearchByReferenceIds02"); + p2.addName().setFamily("testSearchByReferenceIdsFamily02").addGiven("testSearchByReferenceIdsGiven02"); + p2.setManagingOrganization(new Reference(o2id.toUnqualifiedVersionless())); + IIdType p2Id = ourClient.create().resource(p2).execute().getId(); + + //@formatter:off + Bundle actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasAnyOfIds(Arrays.asList(o1id.getIdPart(), o2id.getIdPart()))) + .encodedJson().prettyPrint().returnBundle(Bundle.class).execute(); + //@formatter:on + Set expectedIds = new HashSet(); + expectedIds.add(p1Id.getIdPart()); + expectedIds.add(p2Id.getIdPart()); + Set actualIds = new HashSet(); + for (BundleEntryComponent ele : actual.getEntry()) { + actualIds.add(ele.getResource().getIdElement().getIdPart()); + } + assertEquals("Expects to retrieve the 2 patients which reference the two different organizations", expectedIds, actualIds); + } + + @Test + public void testSearchByResourceChain() { + + Organization o1 = new Organization(); + o1.setName("testSearchByResourceChainName01"); + IdType o1id = (IdType) ourClient.create().resource(o1).execute().getId().toUnqualifiedVersionless(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); + p1.addName().setFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); + p1.setManagingOrganization(new Reference(o1id.toUnqualifiedVersionless())); + IdType p1Id = (IdType) ourClient.create().resource(p1).execute().getId(); + + //@formatter:off + Bundle actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasId(o1id.getIdPart())) + .encodedJson().prettyPrint().returnBundle(Bundle.class).execute(); + //@formatter:on + assertEquals(1, actual.getEntry().size()); + assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + + //@formatter:off + actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasId(o1id.getValue())) + .encodedJson().prettyPrint().returnBundle(Bundle.class).execute(); + //@formatter:on + assertEquals(1, actual.getEntry().size()); + assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + + } + + @Test + public void testSearchInvalidParam() throws Exception { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily("testSearchWithMixedParams").addGiven("Joe"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + // should be subject._id + HttpGet httpPost = new HttpGet(ourServerBase + "/Observation?subject.id=FOO"); + + CloseableHttpResponse resp = ourHttpClient.execute(httpPost); + try { + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + assertThat(respString, containsString("Invalid parameter chain: subject.id")); + assertEquals(400, resp.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + ourLog.info("Outgoing post: {}", httpPost); + } + + @Test + public void testSearchLastUpdatedParamRp() throws InterruptedException { + String methodName = "testSearchLastUpdatedParamRp"; + + int sleep = 100; + Thread.sleep(sleep); + + DateTimeType beforeAny = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI); + IIdType id1a; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily(methodName).addGiven("Joe"); + id1a = (IdType) ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless(); + } + IIdType id1b; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily(methodName + "XXXX").addGiven("Joe"); + id1b = (IdType) ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless(); + } + + Thread.sleep(1100); + DateTimeType beforeR2 = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI); + Thread.sleep(1100); + + IIdType id2; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily(methodName).addGiven("John"); + id2 = (IdType) ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless(); + } + + { + //@formatter:off + Bundle found = ourClient.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + List patients = toUnqualifiedVersionlessIds(found); + assertThat(patients, hasItems(id1a, id1b, id2)); + } + { + //@formatter:off + Bundle found = ourClient.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeAny, null)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + List patients = toUnqualifiedVersionlessIds(found); + assertThat(patients, hasItems(id1a, id1b, id2)); + } + { + //@formatter:off + Bundle found = ourClient.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeR2, null)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + List patients = toUnqualifiedVersionlessIds(found); + assertThat(patients, hasItems(id2)); + assertThat(patients, not(hasItems(id1a, id1b))); + } + { + //@formatter:off + Bundle found = ourClient.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeAny, beforeR2)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + List patients = toUnqualifiedVersionlessIds(found); + assertThat(patients.toString(), patients, not(hasItems(id2))); + assertThat(patients.toString(), patients, (hasItems(id1a, id1b))); + } + { + //@formatter:off + Bundle found = ourClient.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(null, beforeR2)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + List patients = toUnqualifiedVersionlessIds(found); + assertThat(patients, (hasItems(id1a, id1b))); + assertThat(patients, not(hasItems(id2))); + } + } + + /** + * See #441 + */ + @Test + public void testSearchMedicationChain() throws Exception { + Medication medication = new Medication(); + medication.getCode().addCoding().setSystem("SYSTEM").setCode("04823543"); + IIdType medId = myMedicationDao.create(medication).getId().toUnqualifiedVersionless(); + + MedicationAdministration ma = new MedicationAdministration(); + ma.setMedication(new Reference(medId)); + IIdType moId = myMedicationAdministrationDao.create(ma).getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/MedicationAdministration?medication.code=04823543"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertThat(responseString, containsString(moId.getIdPart())); + } finally { + response.close(); + } + + } + + @SuppressWarnings("unused") + @Test + public void testSearchPagingKeepsOldSearches() throws Exception { + String methodName = "testSearchPagingKeepsOldSearches"; + IIdType pid1; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily(methodName).addGiven("Joe"); + pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + for (int i = 1; i <= 20; i++) { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue(Integer.toString(i)); + patient.addName().setFamily(methodName).addGiven("Joe"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + List linkNext = Lists.newArrayList(); + for (int i = 0; i < 100; i++) { + Bundle bundle = ourClient.search().forResource(Patient.class).where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches")).count(5).returnBundle(Bundle.class).execute(); + assertTrue(isNotBlank(bundle.getLink("next").getUrl())); + assertEquals(5, bundle.getEntry().size()); + linkNext.add(bundle.getLink("next").getUrl()); + } + + int index = 0; + for (String nextLink : linkNext) { + ourLog.info("Fetching index {}", index++); + Bundle b = ourClient.fetchResourceFromUrl(Bundle.class, nextLink); + assertEquals(5, b.getEntry().size()); + } + } + + private void testSearchReturnsResults(String search) throws IOException, ClientProtocolException { + int matches; + HttpGet get = new HttpGet(ourServerBase + search); + CloseableHttpResponse response = ourHttpClient.execute(get); + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(resp); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp); + matches = bundle.getTotal(); + + assertThat(matches, greaterThan(0)); + } + + @Test + public void testSearchReturnsSearchDate() throws Exception { + Date before = new Date(); + Thread.sleep(1); + + //@formatter:off + Bundle found = ourClient + .search() + .forResource(Patient.class) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + Thread.sleep(1); + Date after = new Date(); + + InstantType updated = found.getMeta().getLastUpdatedElement(); + assertNotNull(updated); + Date value = updated.getValue(); + assertNotNull(value); + ourLog.info(value.getTime() + ""); + ourLog.info(before.getTime() + ""); + assertTrue(value.after(before)); + assertTrue(new InstantDt(value) + " should be before " + new InstantDt(after), value.before(after)); + } + + @Test + public void testSearchReusesNoParams() throws Exception { + List resources = new ArrayList(); + for (int i = 0; i < 50; i++) { + Organization org = new Organization(); + org.setName("HELLO"); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + myDaoConfig.setReuseCachedSearchResultsForMillis(1000L); + + Bundle result1 = ourClient + .search() + .forResource("Organization") + .returnBundle(Bundle.class) + .execute(); + + final String uuid1 = toSearchUuidFromLinkNext(result1); + + Bundle result2 = ourClient + .search() + .forResource("Organization") + .returnBundle(Bundle.class) + .execute(); + + final String uuid2 = toSearchUuidFromLinkNext(result2); + + assertEquals(uuid1, uuid2); + } + + @Test + public void testSearchReusesResultsDisabled() throws Exception { + List resources = new ArrayList(); + for (int i = 0; i < 50; i++) { + Organization org = new Organization(); + org.setName("HELLO"); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + + Bundle result1 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + final String uuid1 = toSearchUuidFromLinkNext(result1); + + Bundle result2 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + final String uuid2 = toSearchUuidFromLinkNext(result2); + + Bundle result3 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + String uuid3 = toSearchUuidFromLinkNext(result3); + + assertNotEquals(uuid1, uuid2); + assertNotEquals(uuid1, uuid3); + } + + @Test + public void testSearchReusesResultsEnabled() throws Exception { + List resources = new ArrayList(); + for (int i = 0; i < 50; i++) { + Organization org = new Organization(); + org.setName("HELLO"); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + myDaoConfig.setReuseCachedSearchResultsForMillis(1000L); + + Bundle result1 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + final String uuid1 = toSearchUuidFromLinkNext(result1); + Search search1 = newTxTemplate().execute(new TransactionCallback() { + @Override + public Search doInTransaction(TransactionStatus theStatus) { + return mySearchEntityDao.findByUuid(uuid1); + } + }); + Date lastReturned1 = search1.getSearchLastReturned(); + + Bundle result2 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + final String uuid2 = toSearchUuidFromLinkNext(result2); + Search search2 = newTxTemplate().execute(new TransactionCallback() { + @Override + public Search doInTransaction(TransactionStatus theStatus) { + return mySearchEntityDao.findByUuid(uuid2); + } + }); + Date lastReturned2 = search2.getSearchLastReturned(); + + assertTrue(lastReturned2.getTime() > lastReturned1.getTime()); + + Thread.sleep(1500); + + Bundle result3 = ourClient + .search() + .forResource("Organization") + .where(Organization.NAME.matches().value("HELLO")) + .count(5) + .returnBundle(Bundle.class) + .execute(); + + String uuid3 = toSearchUuidFromLinkNext(result3); + + assertEquals(uuid1, uuid2); + assertNotEquals(uuid1, uuid3); + } + + @Test + public void testSearchReusesResultsEnabledNoParams() throws Exception { + List resources = new ArrayList(); + for (int i = 0; i < 50; i++) { + Organization org = new Organization(); + org.setName("HELLO"); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + myDaoConfig.setReuseCachedSearchResultsForMillis(100000L); + + Bundle result1 = ourClient + .search() + .forResource("Organization") + .returnBundle(Bundle.class) + .execute(); + + final String uuid1 = toSearchUuidFromLinkNext(result1); + Search search1 = newTxTemplate().execute(new TransactionCallback() { + @Override + public Search doInTransaction(TransactionStatus theStatus) { + return mySearchEntityDao.findByUuid(uuid1); + } + }); + Date lastReturned1 = search1.getSearchLastReturned(); + + Bundle result2 = ourClient + .search() + .forResource("Organization") + .returnBundle(Bundle.class) + .execute(); + + final String uuid2 = toSearchUuidFromLinkNext(result2); + Search search2 = newTxTemplate().execute(new TransactionCallback() { + @Override + public Search doInTransaction(TransactionStatus theStatus) { + return mySearchEntityDao.findByUuid(uuid2); + } + }); + Date lastReturned2 = search2.getSearchLastReturned(); + + assertTrue(lastReturned2.getTime() > lastReturned1.getTime()); + + assertEquals(uuid1, uuid2); + } + + /** + * See #316 + */ + @Test + public void testSearchThenTagThenSearch() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + ourClient.create().resource(patient).execute(); + + //@formatter:off + Bundle response = ourClient + .search() + .forResource(Patient.class) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + patient = (Patient) response.getEntry().get(0).getResource(); + + //@formatter:off + ourClient + .meta() + .add() + .onResource(patient.getIdElement()) + .meta(new Meta().addTag("http://system", "tag1", "display")) + .execute(); + //@formatter:on + + //@formatter:off + response = ourClient + .search() + .forResource(Patient.class) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + patient = (Patient) response.getEntry().get(0).getResource(); + assertEquals(1, patient.getMeta().getTag().size()); + } + + @Test + public void testSearchTokenParamNoValue() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1"); + patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem") + .setDisplay("testSearchTokenParamDisplay"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002"); + patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2"); + myPatientDao.create(patient, mySrd); + + //@formatter:off + Bundle response = ourClient + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.hasSystemWithAnyCode("urn:system")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(2, response.getEntry().size()); + } + + @Test + public void testSearchWithEmptyParameter() throws Exception { + Observation obs= new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + ourClient.create().resource(obs).execute(); + + testSearchWithEmptyParameter("/Observation?value-quantity="); + testSearchWithEmptyParameter("/Observation?code=bar&value-quantity="); + testSearchWithEmptyParameter("/Observation?value-date="); + testSearchWithEmptyParameter("/Observation?code=bar&value-date="); + testSearchWithEmptyParameter("/Observation?value-concept="); + testSearchWithEmptyParameter("/Observation?code=bar&value-concept="); + } + + private void testSearchWithEmptyParameter(String url) throws IOException, ClientProtocolException { + HttpGet get = new HttpGet(ourServerBase + url); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); + assertEquals(1, bundle.getEntry().size()); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + + @Test + public void testSearchWithInclude() throws Exception { + Organization org = new Organization(); + org.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude01"); + IdType orgId = (IdType) ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId(); + + Patient pat = new Patient(); + pat.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude02"); + pat.getManagingOrganization().setReferenceElement(orgId.toUnqualifiedVersionless()); + ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId(); + + //@formatter:off + Bundle found = ourClient + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("urn:system:rpdstu2","testSearchWithInclude02")) + .include(Patient.INCLUDE_ORGANIZATION) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(2, found.getEntry().size()); + assertEquals(Patient.class, found.getEntry().get(0).getResource().getClass()); + assertEquals(SearchEntryMode.MATCH, found.getEntry().get(0).getSearch().getMode()); + assertEquals(Organization.class, found.getEntry().get(1).getResource().getClass()); + assertEquals(SearchEntryMode.INCLUDE, found.getEntry().get(1).getSearch().getMode()); + } + + @Test() + public void testSearchWithInvalidNumberPrefix() throws Exception { + try { + //@formatter:off + ourClient + .search() + .forResource(Encounter.class) + .where(Encounter.LENGTH.withPrefix(ParamPrefixEnum.ENDS_BEFORE).number(100)) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Unable to handle number prefix \"eb\" for value: eb100")); + } + } + + @Test() + public void testSearchWithInvalidQuantityPrefix() throws Exception { + Observation o = new Observation(); + o.getCode().setText("testSearchWithInvalidSort"); + myObservationDao.create(o, mySrd); + try { + //@formatter:off + ourClient + .search() + .forResource(Observation.class) + .where(Observation.VALUE_QUANTITY.withPrefix(ParamPrefixEnum.ENDS_BEFORE).number(100).andNoUnits()) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Unable to handle quantity prefix \"eb\" for value: eb100||")); + } + } + + @Test() + public void testSearchNegativeNumbers() throws Exception { + Observation o = new Observation(); + o.setValue(new Quantity().setValue(new BigDecimal("-10"))); + String oid1 = myObservationDao.create(o, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Observation o2 = new Observation(); + o2.setValue(new Quantity().setValue(new BigDecimal("-20"))); + String oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless().getValue(); + + HttpGet get = new HttpGet(ourServerBase + "/Observation?value-quantity=gt-15"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); + + List ids = toUnqualifiedVersionlessIdValues(bundle); + assertThat(ids, contains(oid1)); + assertThat(ids, not(contains(oid2))); + } finally { + IOUtils.closeQuietly(resp); + } + + } + + @Test(expected = InvalidRequestException.class) + public void testSearchWithInvalidSort() throws Exception { + Observation o = new Observation(); + o.getCode().setText("testSearchWithInvalidSort"); + myObservationDao.create(o, mySrd); + //@formatter:off + ourClient + .search() + .forResource(Observation.class) + .sort().ascending(Observation.CODE_VALUE_QUANTITY) // composite sort not supported yet + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + } + + @Test + public void testSearchWithMissing() throws Exception { + ourLog.info("Starting testSearchWithMissing"); + + String methodName = "testSearchWithMissing"; + + Organization org = new Organization(); + IIdType deletedIdMissingTrue = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless(); + ourClient.delete().resourceById(deletedIdMissingTrue).execute(); + + org = new Organization(); + org.setName("Help I'm a Bug"); + IIdType deletedIdMissingFalse = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless(); + ourClient.delete().resourceById(deletedIdMissingFalse).execute(); + + List resources = new ArrayList(); + for (int i = 0; i < 20; i++) { + org = new Organization(); + org.setName(methodName + "_0" + i); + resources.add(org); + } + ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); + + org = new Organization(); + org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01"); + org.setName(methodName + "name"); + IIdType orgNotMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless(); + + org = new Organization(); + org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01"); + IIdType orgMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless(); + + { + //@formatter:off + Bundle found = ourClient + .search() + .forResource(Organization.class) + .where(Organization.NAME.isMissing(false)) + .count(100) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + List list = toUnqualifiedVersionlessIds(found); + ourLog.info(methodName + ": " + list.toString()); + ourLog.info("Wanted " + orgNotMissing + " and not " + deletedIdMissingFalse + " but got " + list.size() + ": " + list); + assertThat("Wanted " + orgNotMissing + " but got " + list.size() + ": " + list, list, containsInRelativeOrder(orgNotMissing)); + assertThat(list, not(containsInRelativeOrder(deletedIdMissingFalse))); + assertThat(list, not(containsInRelativeOrder(orgMissing))); + } + + //@formatter:off + Bundle found = ourClient + .search() + .forResource(Organization.class) + .where(Organization.NAME.isMissing(true)) + .count(100) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + List list = toUnqualifiedVersionlessIds(found); + ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing); + assertThat(list, not(containsInRelativeOrder(orgNotMissing))); + assertThat(list, not(containsInRelativeOrder(deletedIdMissingTrue))); + assertThat("Wanted " + orgMissing + " but found: " + list, list, containsInRelativeOrder(orgMissing)); + } + + @Test + public void testSearchWithMissing2() throws Exception { + checkParamMissing(Observation.SP_CODE); + checkParamMissing(Observation.SP_CATEGORY); + checkParamMissing(Observation.SP_VALUE_STRING); + checkParamMissing(Observation.SP_ENCOUNTER); + checkParamMissing(Observation.SP_DATE); + } + + @Test + public void testSearchWithMissingDate2() throws Exception { + MedicationRequest mr1 = new MedicationRequest(); + mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); + IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); + + MedicationRequest mr2 = new MedicationRequest(); + mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/MedicationRequest?date:missing=false"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); + + List ids = toUnqualifiedVersionlessIdValues(bundle); + assertThat(ids, contains(id1.getValue())); + assertThat(ids, not(contains(id2.getValue()))); + } finally { + IOUtils.closeQuietly(resp); + } + + } + + /** + * See #411 + * + * Let's see if we can reproduce this issue in JPA + */ + @Test + public void testSearchWithMixedParams() throws Exception { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily("testSearchWithMixedParams").addGiven("Joe"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + HttpPost httpPost = new HttpPost(ourServerBase + "/Patient/_search?_format=application/xml"); + httpPost.addHeader("Cache-Control", "no-cache"); + List parameters = Lists.newArrayList(); + parameters.add(new BasicNameValuePair("name", "Smith")); + httpPost.setEntity(new UrlEncodedFormEntity(parameters)); + + ourLog.info("Outgoing post: {}", httpPost); + + CloseableHttpResponse status = ourHttpClient.execute(httpPost); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @Test + public void testSearchWithTextInexactMatch() throws Exception { + Observation obs = new Observation(); + obs.getCode().setText("THIS_IS_THE_TEXT"); + obs.getCode().addCoding().setSystem("SYSTEM").setCode("CODE").setDisplay("THIS_IS_THE_DISPLAY"); + ourClient.create().resource(obs).execute(); + + testSearchReturnsResults("/Observation?code%3Atext=THIS_IS_THE_TEXT"); + testSearchReturnsResults("/Observation?code%3Atext=THIS_IS_THE_"); + testSearchReturnsResults("/Observation?code%3Atext=this_is_the_"); + testSearchReturnsResults("/Observation?code%3Atext=THIS_IS_THE_DISPLAY"); + testSearchReturnsResults("/Observation?code%3Atext=THIS_IS_THE_disp"); + } + + /** + * See #198 + */ + @Test + public void testSortFromResourceProvider() { + Patient p; + String methodName = "testSortFromResourceProvider"; + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Daniel").setFamily("Adams"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Aaron").setFamily("Alexis"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Carol").setFamily("Allen"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Ruth").setFamily("Black"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Brian").setFamily("Brooks"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Susan").setFamily("Clark"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Amy").setFamily("Clark"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Anthony").setFamily("Coleman"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Steven").setFamily("Coleman"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Lisa").setFamily("Coleman"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Ruth").setFamily("Cook"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Betty").setFamily("Davis"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Joshua").setFamily("Diaz"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Brian").setFamily("Gracia"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Stephan").setFamily("Graham"); + ourClient.create().resource(p).execute(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addGiven("Sarah").setFamily("Graham"); + ourClient.create().resource(p).execute(); + + Bundle resp = ourClient + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", methodName)) + .sort().ascending(Patient.FAMILY) + .sort().ascending(Patient.GIVEN) + .count(100) + .returnBundle(Bundle.class) + .execute(); + + List names = toNameList(resp); + + ourLog.info(StringUtils.join(names, '\n')); + + assertThat(names, contains( // this matches in order only + "Daniel Adams", + "Aaron Alexis", + "Carol Allen", + "Ruth Black", + "Brian Brooks", + "Amy Clark", + "Susan Clark", + "Anthony Coleman", + "Lisa Coleman", + "Steven Coleman", + "Ruth Cook", + "Betty Davis", + "Joshua Diaz", + "Brian Gracia", + "Sarah Graham", + "Stephan Graham")); + + } + + /** + * Test for issue #60 + */ + @Test + public void testStoreUtf8Characters() throws Exception { + Organization org = new Organization(); + org.setName("測試醫院"); + org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01"); + IdType orgId = (IdType) ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId(); + + // Read back directly from the DAO + { + Organization returned = myOrganizationDao.read(orgId, mySrd); + String val = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned); + ourLog.info(val); + assertThat(val, containsString("")); + } + // Read back through the HTTP API + { + Organization returned = ourClient.read(Organization.class, orgId.getIdPart()); + String val = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned); + ourLog.info(val); + assertThat(val, containsString("")); + } + } + + @Test + public void testTransaction() throws Exception { + String contents = loadClasspath("/update.xml"); + HttpPost post = new HttpPost(ourServerBase); + post.setEntity(new StringEntity(contents, ContentType.create("application/xml+fhir", "UTF-8"))); + CloseableHttpResponse resp = ourHttpClient.execute(post); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String output = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + } finally { + resp.close(); + } + } + + @Test + public void testTryToCreateResourceWithReferenceThatDoesntExist() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testTryToCreateResourceWithReferenceThatDoesntExist01"); + p1.addName().setFamily("testTryToCreateResourceWithReferenceThatDoesntExistFamily01").addGiven("testTryToCreateResourceWithReferenceThatDoesntExistGiven01"); + p1.setManagingOrganization(new Reference("Organization/99999999999")); + + try { + ourClient.create().resource(p1).execute().getId(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Organization/99999999999")); + } + + } + + @Test + public void testUpdateInvalidReference() throws IOException, Exception { + String methodName = "testUpdateInvalidReference"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPut post = new HttpPut(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertEquals(400, response.getStatusLine().getStatusCode()); + OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString); + assertThat(oo.getIssue().get(0).getDiagnostics(), + containsString("Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])")); + } finally { + response.close(); + } + } + + @Test + public void testUpdateInvalidReference2() throws IOException, Exception { + String methodName = "testUpdateInvalidReference2"; + + Patient pt = new Patient(); + pt.setId("2"); + pt.addName().setFamily(methodName); + String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); + + HttpPut post = new HttpPut(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseString); + assertThat(responseString, containsString("Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])")); + assertThat(responseString, containsString("")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + // Y + @Test + public void testValidateResourceHuge() throws IOException { + + Patient patient = new Patient(); + patient.addName().addGiven("James" + StringUtils.leftPad("James", 1000000, 'A')); + ; + patient.setBirthDateElement(new DateType("2011-02-02")); + + Parameters input = new Parameters(); + input.addParameter().setName("resource").setResource(patient); + + String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input); + ourLog.debug(inputStr); + + HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); + post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + @Test + public void testValidateResourceInstanceOnServer() throws IOException { + + Patient patient = new Patient(); + patient.addName().addGiven("James"); + patient.setBirthDateElement(new DateType("2011-02-02")); + patient.addContact().setGender(AdministrativeGender.MALE); + patient.addCommunication().setPreferred(true); // missing language + + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "/$validate"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(412, response.getStatusLine().getStatusCode()); + assertThat(resp, containsString("SHALL at least contain a contact's details or a reference to an organization")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + @Test + public void testValidateResourceWithId() throws IOException { + + Patient patient = new Patient(); + patient.setId("A123"); + patient.addName().addGiven("James"); + patient.setBirthDateElement(new DateType("2011-02-02")); + myPatientDao.update(patient, mySrd); + + Parameters input = new Parameters(); + input.addParameter().setName("resource").setResource(patient); + + String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input); + ourLog.info(inputStr); + + HttpPost post = new HttpPost(ourServerBase + "/Patient/A123/$validate"); + post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + // Y + @Test + public void testValidateResourceWithNoIdParameters() throws IOException { + + Patient patient = new Patient(); + patient.addName().addGiven("James"); + patient.setBirthDateElement(new DateType("2011-02-02")); + + Parameters input = new Parameters(); + input.addParameter().setName("resource").setResource(patient); + + String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input); + HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate?_pretty=true"); + post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, not(containsString("Resource has no id"))); + assertThat(resp, containsString("
No issues detected during validation
")); + assertThat(resp, + stringContainsInOrder("", "", "", "", + "")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + @Test + public void testValidateResourceWithNoIdRaw() throws IOException { + + Patient patient = new Patient(); + patient.addName().addGiven("James"); + patient.setBirthDateElement(new DateType("2011-02-02")); + + String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(patient); + HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); + post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + CloseableHttpResponse response = ourHttpClient.execute(post); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, not(containsString("Resource has no id"))); + assertThat(resp, containsString("
No issues detected during validation
")); + assertThat(resp, + stringContainsInOrder("", "", "", "", + "")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + + @Test + public void testValueSetExpandOperation() throws IOException { + CodeSystem cs = myFhirCtx.newXmlParser().parseResource(CodeSystem.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-cs.xml"))); + ourClient.create().resource(cs).execute(); + + ValueSet upload = myFhirCtx.newXmlParser().parseResource(ValueSet.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-vs.xml"))); + IIdType vsid = ourClient.create().resource(upload).execute().getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + + /* + * Filter with display name + */ + + get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand?filter=systolic"); + response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + + } + + private String toStr(Date theDate) { + return new InstantDt(theDate).getValueAsString(); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} 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 new file mode 100644 index 00000000000..8c11ff1d3a2 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -0,0 +1,533 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.FilterOperator; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4ValueSetTest.class); + private IIdType myExtensionalVsId; + private IIdType myLocalValueSetId; + private ValueSet myLocalVs; + + @Before + @Transactional + public void before02() throws IOException { + CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + myCodeSystemDao.create(cs, mySrd); + + ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); + } + + /** + * #516 + */ + @Test + public void testInvalidFilter() throws Exception { + String string = IOUtils.toString(getClass().getResourceAsStream("/bug_516_invalid_expansion.json"), StandardCharsets.UTF_8); + HttpPost post = new HttpPost(ourServerBase+"/ValueSet/%24expand"); + post.setEntity(new StringEntity(string, ContentType.parse(ca.uhn.fhir.rest.api.Constants.CT_FHIR_JSON_NEW))); + + CloseableHttpResponse resp = ourHttpClient.execute(post); + try { + + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + + ourLog.info(resp.toString()); + + assertEquals(400, resp.getStatusLine().getStatusCode()); + assertThat(respString, containsString("Unknown FilterOperator code 'n'")); + + }finally { + IOUtils.closeQuietly(resp); + } + } + + private CodeSystem createExternalCs() { + IFhirResourceDao codeSystemDao = myCodeSystemDao; + IResourceTableDao resourceTableDao = myResourceTableDao; + IHapiTerminologySvc termSvc = myTermSvc; + + return createExternalCs(codeSystemDao, resourceTableDao, termSvc, mySrd); + } + + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, IHapiTerminologySvc theTermSvc, ServletRequestDetails theRequestDetails) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = theCodeSystemDao.create(codeSystem, theRequestDetails).getId().toUnqualified(); + + ResourceTable table = theResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA"); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA"); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB"); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB"); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); + cs.getConcepts().add(parentB); + + theTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + return codeSystem; + } + + private void createExternalCsAndLocalVs() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVs(codeSystem); + } + + private void createExternalCsAndLocalVsWithUnknownCode() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVsWithUnknownCode(codeSystem); + } + + private void createLocalCsAndVs() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code B") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + //@formatter:on + myCodeSystemDao.create(codeSystem, mySrd); + + createLocalVs(codeSystem); + } + + private void createLocalVs(CodeSystem codeSystem) { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem(codeSystem.getUrl()); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childAA"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + + private void createLocalVsPointingAtBuiltInCodeSystem() { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem("http://hl7.org/fhir/v3/MaritalStatus"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + + private void createLocalVsWithUnknownCode(CodeSystem codeSystem) { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem(codeSystem.getUrl()); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childFOOOOOOO"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + + @Test + public void testExpandById() throws IOException { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myExtensionalVsId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + } + + @Test + public void testExpandByIdentifier() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + + } + + // + + @Test + public void testExpandByIdWithFilter() throws IOException { + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myExtensionalVsId) + .named("expand") + .withParameter(Parameters.class, "filter", new StringType("first")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, not(containsString(""))); + + } + + @Test + public void testExpandByValueSet() throws IOException { + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + //@formatter:off + assertThat(resp, stringContainsInOrder( + "", + "")); + //@formatter:on + + } + + @Test + public void testExpandInlineVsAgainstBuiltInCs() throws IOException { + createLocalVsPointingAtBuiltInCodeSystem(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + } + + @Test + public void testExpandInlineVsAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalVs); + myLocalVs.setId(""); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + + @Test + public void testExpandInvalidParams() throws IOException { + //@formatter:off + try { + ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: $expand operation at the type level (no ID specified) requires an identifier or a valueSet as a part of the request", e.getMessage()); + } + //@formatter:on + + //@formatter:off + try { + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-r4.xml"); + ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.", e.getMessage()); + } + //@formatter:on + + //@formatter:off + try { + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-r4.xml"); + ourClient + .operation() + .onInstance(myExtensionalVsId) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.", e.getMessage()); + } + //@formatter:on + + } + + @Test + public void testExpandLocalVsAgainstBuiltInCs() throws IOException { + createLocalVsPointingAtBuiltInCodeSystem(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + } + + + + + + @Test + public void testExpandLocalVsAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandLocalVsCanonicalAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "identifier", new UriType(URL_MY_VALUE_SET)) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandLocalVsWithUnknownCode() throws IOException { + createExternalCsAndLocalVsWithUnknownCode(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + try { + ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Invalid filter criteria - code does not exist: {http://example.com/my_code_system}childFOOOOOOO", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testValiedateCodeAgainstBuiltInSystem() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new StringType("BRN")) + .andParameter("identifier", new StringType("http://hl7.org/fhir/ValueSet/v2-0487")) + .andParameter("system", new StringType("http://hl7.org/fhir/v2/0487")) + .useHttpGet() + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(true, ((BooleanType)respParam.getParameter().get(0).getValue()).getValue().booleanValue()); + + assertEquals("message", respParam.getParameter().get(1).getName()); + assertThat(((StringType)respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); + + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Burn", ((StringType)respParam.getParameter().get(2).getValue()).getValue()); + } + + + @Test + public void testValidateCodeOperationByCodeAndSystemInstance() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myExtensionalVsId) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(true, ((BooleanType)respParam.getParameter().get(0).getValue()).booleanValue()); + } + + @Test + public void testValidateCodeOperationByCodeAndSystemType() { + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(true, ((BooleanType)respParam.getParameter().get(0).getValue()).booleanValue()); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java new file mode 100644 index 00000000000..65402320ee2 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.util.TestUtil; + +public class ServerR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerR4Test.class); + + + + /** + * See #519 + */ + @Test + public void saveIdParamOnlyAppearsOnce() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/metadata?_pretty=true&_format=xml"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + ourLog.info(resp.toString()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + + CapabilityStatement cs = myFhirCtx.newXmlParser().parseResource(CapabilityStatement.class, respString); + + for (CapabilityStatementRestResourceComponent nextResource : cs.getRest().get(0).getResource()) { + ourLog.info("Testing resource: " + nextResource.getType()); + Set sps = new HashSet(); + for (CapabilityStatementRestResourceSearchParamComponent nextSp : nextResource.getSearchParam()) { + if (sps.add(nextSp.getName()) == false) { + fail("Duplicate search parameter " + nextSp.getName() + " for resource " + nextResource.getType()); + } + } + + if (!sps.contains("_id")) { + fail("No search parameter _id for resource " + nextResource.getType()); + } + } + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java new file mode 100644 index 00000000000..8334a1d3bd8 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java @@ -0,0 +1,97 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.util.AopTestUtils; + +import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; +import ca.uhn.fhir.rest.gclient.IClientExecutable; +import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.util.TestUtil; + +public class StaleSearchDeletingSvcR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcR4Test.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @After() + public void after() throws Exception { + super.after(); + StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); + staleSearchDeletingSvc.setCutoffSlackForUnitTest(StaleSearchDeletingSvcImpl.DEFAULT_CUTOFF_SLACK); + } + + @Before + public void before() throws Exception { + super.before(); + StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); + staleSearchDeletingSvc.setCutoffSlackForUnitTest(0); + } + + @Test + public void testEverythingInstanceWithContentFilter() throws Exception { + + for (int i = 0; i < 20; i++) { + Patient pt1 = new Patient(); + pt1.addName().setFamily("Everything").addGiven("Arthur"); + myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); + } + + //@formatter:off + IClientExecutable, Bundle> search = ourClient + .search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("Everything")) + .returnBundle(Bundle.class); + //@formatter:on + + Bundle resp1 = search.execute(); + + for (int i = 0; i < 20; i++) { + search.execute(); + } + + BundleLinkComponent nextLink = resp1.getLink("next"); + assertNotNull(nextLink); + String nextLinkUrl = nextLink.getUrl(); + assertThat(nextLinkUrl, not(blankOrNullString())); + + Bundle resp2 = ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp2)); + + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + + ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute(); + + Thread.sleep(20); + myDaoConfig.setExpireSearchResultsAfterMillis(10); + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + + try { + ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute(); + fail(); + } catch (ResourceGoneException e) { + assertThat(e.getMessage(), containsString("does not exist and may have expired")); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java new file mode 100644 index 00000000000..df5cdcafbac --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java @@ -0,0 +1,515 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.junit.Assert.*; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.*; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; + +import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; + +public class SubscriptionsR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionsR4Test.class); + + private static final String WEBSOCKET_PATH = "/websocket/r4"; + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + + @Override + public void beforeCreateInterceptor() { + super.beforeCreateInterceptor(); + + SubscriptionsRequireManualActivationInterceptorR4 interceptor = new SubscriptionsRequireManualActivationInterceptorR4(); + interceptor.setDao(mySubscriptionDao); + myDaoConfig.getInterceptors().add(interceptor); + } + + @Before + public void beforeEnableScheduling() { + myDaoConfig.setSchedulingDisabled(false); + } + + + private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException { + /* + * In a separate thread, start a polling for new resources. Normally the scheduler would + * take care of this, but that can take longer which makes the unit tests run much slower + * so we simulate that part.. + */ + new Thread() { + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + ourLog.warn("Interrupted", e); + } + ourLog.info("About to poll in separate thread"); + mySubscriptionDao.pollForNewUndeliveredResources(); + ourLog.info("Done poll in separate thread"); + }}.start(); + + ourLog.info("Entering loop"); + for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) { + ourLog.debug("Starting"); + if (socket.myError != null) { + fail(socket.myError); + } + if (socket.myPingCount >= wantPingCount) { + ourLog.info("Breaking loop"); + break; + } + ourLog.debug("Sleeping"); + Thread.sleep(100); + } + + ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError); + + assertNull(socket.myError, socket.myError); + assertEquals(wantPingCount, socket.myPingCount); + } + + private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception { + client.stop(); + + /* + * When websocket closes, the subscription is automatically deleted. This + * can cause deadlocks if the test returns too quickly since it also + * tries to delete the subscription + */ + Bundle found = null; + for (int i = 0; i < 10; i++) { + found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute(); + if (found.getEntry().size() > 0) { + Thread.sleep(200); + } else { + break; + } + } + assertEquals(0, found.getEntry().size()); + + } + + @Test + public void testCreateInvalidNoStatus() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("application/fhir+json"); + subs.getChannel().setEndpoint("http://localhost:8888"); + subs.setCriteria("Observation?identifier=123"); + try { + ourClient.create().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Can not create resource: Subscription.status must be populated", e.getMessage()); + } + + subs.setId("ABC"); + try { + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Can not create resource: Subscription.status must be populated", e.getMessage()); + } + + subs.setStatus(SubscriptionStatus.REQUESTED); + ourClient.update().resource(subs).execute(); + } + + @Test + public void testCreateInvalidWrongStatus() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.setStatus(SubscriptionStatus.ACTIVE); + subs.setCriteria("Observation?identifier=123"); + try { + ourClient.create().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); + } + + subs.setId("ABC"); + try { + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); + } + } + + @Test + public void testSubscriptionDynamic() throws Exception { + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testSubscriptionDynamic"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + String criteria = "Observation?subject=Patient/" + pId.getIdPart(); + DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON); + WebSocketClient client = new WebSocketClient(); + try { + client.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); + client.connect(socket, echoUri, new ClientUpgradeRequest()); + ourLog.info("Connecting to : {}", echoUri); + + sleepUntilPingCount(socket, 1); + + mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + sleepUntilPingCount(socket, 3); + + obs = (Observation) socket.myReceived.get(0); + assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + obs = (Observation) socket.myReceived.get(1); + assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + sleepUntilPingCount(socket, 4); + + obs = (Observation) socket.myReceived.get(2); + assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + } finally { + try { + stopClientAndWaitForStopped(client); + } catch (Exception e) { + ourLog.error("Failure", e); + fail(e.getMessage()); + } + } + + } + + + @Test + public void testSubscriptionDynamicXml() throws Exception { + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testSubscriptionDynamic"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml"; + DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML); + WebSocketClient client = new WebSocketClient(); + try { + client.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); + client.connect(socket, echoUri, new ClientUpgradeRequest()); + ourLog.info("Connecting to : {}", echoUri); + + sleepUntilPingCount(socket, 1); + + mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); + + Observation obs = new Observation(); + obs.getMeta().addProfile("http://foo"); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + sleepUntilPingCount(socket, 3); + + obs = (Observation) socket.myReceived.get(0); + assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + obs = (Observation) socket.myReceived.get(1); + assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + sleepUntilPingCount(socket, 4); + + obs = (Observation) socket.myReceived.get(2); + assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); + + } finally { + try { + stopClientAndWaitForStopped(client); + } catch (Exception e) { + ourLog.error("Failure", e); + fail(e.getMessage()); + } + } + + } + + @Test + public void testSubscriptionSimple() throws Exception { + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPollDelay(0); + + String methodName = "testSubscriptionResourcesAppear"; + Patient p = new Patient(); + p.addName().setFamily(methodName); + IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Subscription subs = new Subscription(); + subs.getMeta().addProfile("http://foo"); + subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); + subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatus.ACTIVE); + String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart(); + + Thread.sleep(100); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + + WebSocketClient client = new WebSocketClient(); + SimpleEchoSocket socket = new SimpleEchoSocket(subsId); + try { + client.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + client.connect(socket, echoUri, request); + ourLog.info("Connecting to : {}", echoUri); + + sleepUntilPingCount(socket, 1); + + obs = new Observation(); + obs.getSubject().setReferenceElement(pId); + obs.setStatus(ObservationStatus.FINAL); + IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + sleepUntilPingCount(socket, 2); + + } finally { + try { + client.stop(); + } catch (Exception e) { + ourLog.error("Failure", e); + fail(e.getMessage()); + } + } + + } + + + @Test + public void testUpdateFails() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("application/fhir+json"); + subs.getChannel().setEndpoint("http://localhost:8888"); + subs.setStatus(SubscriptionStatus.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless(); + + subs.setId(id); + + try { + subs.setStatus(SubscriptionStatus.ACTIVE); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage()); + } + + try { + subs.setStatus((SubscriptionStatus) null); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Can not update resource: Subscription.status must be populated", e.getMessage()); + } + + subs.setStatus(SubscriptionStatus.OFF); + } + + + @Test + public void testUpdateToInvalidStatus() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelType.RESTHOOK); + subs.getChannel().setPayload("application/fhir+json"); + subs.getChannel().setEndpoint("http://localhost:8888"); + subs.setCriteria("Observation?identifier=123"); + subs.setStatus(SubscriptionStatus.REQUESTED); + IIdType id = ourClient.create().resource(subs).execute().getId(); + subs.setId(id); + + try { + subs.setStatus(SubscriptionStatus.ACTIVE); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage()); + } + + try { + subs.setStatus((SubscriptionStatus) null); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Can not update resource: Subscription.status must be populated", e.getMessage()); + } + + subs.setStatus(SubscriptionStatus.OFF); + ourClient.update().resource(subs).execute(); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + public class BaseSocket { + protected String myError; + protected boolean myGotBound; + protected int myPingCount; + protected String mySubsId; + + } + + /** + * Basic Echo Client Socket + */ + @WebSocket(maxTextMessageSize = 64 * 1024) + public class DynamicEchoSocket extends BaseSocket { + + private String myCriteria; + private EncodingEnum myEncoding; + private List myReceived = new ArrayList(); + @SuppressWarnings("unused") + private Session session; + + public DynamicEchoSocket(String theCriteria, EncodingEnum theEncoding) { + myCriteria = theCriteria; + myEncoding = theEncoding; + } + + @OnWebSocketConnect + public void onConnect(Session session) { + ourLog.info("Got connect: {}", session); + this.session = session; + try { + String sending = "bind " + myCriteria; + ourLog.info("Sending: {}", sending); + session.getRemote().sendString(sending); + } catch (Throwable t) { + ourLog.error("Failure", t); + } + } + + @OnWebSocketMessage + public void onMessage(String theMsg) { + ourLog.info("Got msg: {}", theMsg); + if (theMsg.startsWith("bound ")) { + myGotBound = true; + mySubsId = (theMsg.substring("bound ".length())); + myPingCount++; + } else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) { + String text = theMsg.substring(("add " + mySubsId + "\n").length()); + IBaseResource res = myEncoding.newParser(myFhirCtx).parseResource(text); + myReceived.add(res); + myPingCount++; + } else { + myError = "Unexpected message: " + theMsg; + } + } + } + + /** + * Basic Echo Client Socket + */ + @WebSocket(maxTextMessageSize = 64 * 1024) + public class SimpleEchoSocket extends BaseSocket { + + @SuppressWarnings("unused") + private Session session; + + public SimpleEchoSocket(String theSubsId) { + mySubsId = theSubsId; + } + + @OnWebSocketConnect + public void onConnect(Session session) { + ourLog.info("Got connect: {}", session); + this.session = session; + try { + String sending = "bind " + mySubsId; + ourLog.info("Sending: {}", sending); + session.getRemote().sendString(sending); + } catch (Throwable t) { + ourLog.error("Failure", t); + } + } + + @OnWebSocketMessage + public void onMessage(String theMsg) { + ourLog.info("Got msg: {}", theMsg); + if (theMsg.equals("bound " + mySubsId)) { + myGotBound = true; + } else if (myGotBound && theMsg.startsWith("ping " + mySubsId)) { + myPingCount++; + } else { + myError = "Unexpected message: " + theMsg; + } + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java new file mode 100644 index 00000000000..0a26b2caf46 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java @@ -0,0 +1,662 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.client.methods.*; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +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.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.r4.model.*; +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.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.jpa.rp.r4.*; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +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.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.ResultSeverityEnum; + +public class SystemProviderR4Test extends BaseJpaR4Test { + + private static RestfulServer myRestServer; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static CloseableHttpClient ourHttpClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderR4Test.class); + private static Server ourServer; + private static String ourServerBase; + private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; + + @Test + public void testTransactionWithInlineConditionalUrl() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI"); + myPatientDao.create(p, mySrd); + + Organization o = new Organization(); + o.addIdentifier().setSystem("urn:oid:2.16.840.1.113883.2.4.6.1").setValue("07-8975469"); + myOrganizationDao.create(o, mySrd); + + //@formatter:off + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + //@formatter:off + + HttpPost req = new HttpPost(ourServerBase); + req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8"))); + + CloseableHttpResponse resp = ourHttpClient.execute(req); + try { + String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(encoded); + + assertThat(encoded, containsString("transaction-response")); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + + } + + + @Test + public void testTransactionDeleteWithDuplicateDeletes() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI"); + IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + ourClient.read().resource(Patient.class).withId(id); + + Bundle inputBundle = new Bundle(); + inputBundle.setType(BundleType.TRANSACTION); + inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue()); + inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue()); + inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?name=Pietercx85ioqWJbI"); + String input = myFhirCtx.newXmlParser().encodeResourceToString(inputBundle); + + HttpPost req = new HttpPost(ourServerBase + "?_pretty=true"); + req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8"))); + + CloseableHttpResponse resp = ourHttpClient.execute(req); + try { + String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(encoded); + + assertThat(encoded, containsString("transaction-response")); + + Bundle response = myFhirCtx.newXmlParser().parseResource(Bundle.class, encoded); + assertEquals(3, response.getEntry().size()); + + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + + try { + ourClient.read().resource(Patient.class).withId(id).execute(); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + + @Before + public void beforeStartServer() throws Exception { + if (myRestServer == null) { + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + + QuestionnaireResourceProviderR4 questionnaireRp = new QuestionnaireResourceProviderR4(); + questionnaireRp.setDao(myQuestionnaireDao); + + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(myObservationDao); + + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(myOrganizationDao); + + RestfulServer restServer = new RestfulServer(ourCtx); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setPlainProviders(mySystemProvider); + + int myPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ourServerBase = "http://localhost:" + myPort + "/fhir/context"; + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourCtx = FhirContext.forR4(); + restServer.setFhirContext(ourCtx); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourHttpClient = builder.build(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.setLogRequestAndResponse(true); + myRestServer = restServer; + } + + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); + } + + @Before + public void before() { + mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); + ourClient.registerInterceptor(mySimpleHeaderInterceptor); + } + + @SuppressWarnings("deprecation") + @After + public void after() { + myRestServer.setUseBrowserFriendlyContentTypes(true); + ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); + } + + @SuppressWarnings("deprecation") + @Test + public void testResponseUsesCorrectContentType() throws Exception { + myRestServer.setUseBrowserFriendlyContentTypes(true); + myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); + + HttpGet get = new HttpGet(ourServerBase); +// get.addHeader("Accept", "application/xml, text/html"); + CloseableHttpResponse http = ourHttpClient.execute(get); + assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/fhir+json")); + } + + + /** + * FOrmat has changed, source is no longer valid + */ + @Test + @Ignore + public void testValidateUsingIncomingResources() throws Exception { + FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport); + RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); + interceptor.addValidatorModule(val); + interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + interceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); + myRestServer.registerInterceptor(interceptor); + try { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml"); + String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + + HttpPost req = new HttpPost(ourServerBase); + req.setEntity(new StringEntity(bundleStr, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8"))); + + CloseableHttpResponse resp = ourHttpClient.execute(req); + try { + String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(encoded); + + //@formatter:off + assertThat(encoded, containsString("Questionnaire/54127-6/_history/")); + //@formatter:on + + for (Header next : resp.getHeaders(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_NAME)) { + ourLog.info(next.toString()); + } + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } finally { + myRestServer.unregisterInterceptor(interceptor); + } + } + + @Test + public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception { + myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); + myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10)); + ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); + myRestServer.registerInterceptor(interceptor); + + for (int i = 0; i < 11; i++) { + Patient p = new Patient(); + p.addName().setFamily("Name" + i); + ourClient.create().resource(p).execute(); + } + + HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); + get.addHeader("Accept", "application/xml, text/html"); + CloseableHttpResponse http = ourHttpClient.execute(get); + + try { + String response = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(response); + assertThat(response, containsString("_format=json")); + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + http.close(); + } + + myRestServer.unregisterInterceptor(interceptor); + } + + @Test + public void testEverythingReturnsCorrectBundleType() throws Exception { + myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); + myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10)); + ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); + myRestServer.registerInterceptor(interceptor); + + for (int i = 0; i < 11; i++) { + Patient p = new Patient(); + p.addName().setFamily("Name" + i); + ourClient.create().resource(p).execute(); + } + + HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); + get.addHeader("Accept", "application/xml+fhir"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + String response = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(response); + assertThat(response, not(containsString("_format"))); + assertEquals(200, http.getStatusLine().getStatusCode()); + + Bundle responseBundle = ourCtx.newXmlParser().parseResource(Bundle.class, response); + assertEquals(BundleType.SEARCHSET, responseBundle.getTypeElement().getValue()); + + } finally { + http.close(); + } + + myRestServer.unregisterInterceptor(interceptor); + } + + @Test + public void testEverythingType() throws Exception { + HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + http.close(); + } + } + + @Test + public void testMarkResourcesForReindexing() throws Exception { + HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(http);; + } + } + + @Transactional(propagation = Propagation.NEVER) + @Test + public void testSuggestKeywords() throws Exception { + + Patient patient = new Patient(); + patient.addName().setFamily("testSuggest"); + IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); + obs.getSubject().setReferenceElement(ptId); + IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.setId(obsId); + obs.getSubject().setReferenceElement(ptId); + obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); + myObservationDao.update(obs, mySrd); + + HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + assertEquals(200, http.getStatusLine().getStatusCode()); + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + + Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output); + assertEquals(2, parameters.getParameter().size()); + assertEquals("keyword", parameters.getParameter().get(0).getPart().get(0).getName()); + assertEquals(("ZXCVBNM"), ((StringType) parameters.getParameter().get(0).getPart().get(0).getValue()).getValueAsString()); + assertEquals("score", parameters.getParameter().get(0).getPart().get(1).getName()); + assertEquals(("1.0"), ((DecimalType) parameters.getParameter().get(0).getPart().get(1).getValue()).getValueAsString()); + + } finally { + http.close(); + } + } + + @Test + public void testSuggestKeywordsInvalid() throws Exception { + Patient patient = new Patient(); + patient.addName().setFamily("testSuggest"); + IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(ptId); + obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); + myObservationDao.create(obs, mySrd); + + HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + assertEquals(400, http.getStatusLine().getStatusCode()); + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertThat(output, containsString("Parameter 'context' must be provided")); + } finally { + http.close(); + } + + get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything"); + http = ourHttpClient.execute(get); + try { + assertEquals(400, http.getStatusLine().getStatusCode()); + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertThat(output, containsString("Parameter 'searchParam' must be provided")); + } finally { + http.close(); + } + + get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=aa"); + http = ourHttpClient.execute(get); + try { + assertEquals(400, http.getStatusLine().getStatusCode()); + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertThat(output, containsString("Parameter 'text' must be provided")); + } finally { + http.close(); + } + + } + + @Test + public void testGetOperationDefinition() { + OperationDefinition op = ourClient.read(OperationDefinition.class, "-s-get-resource-counts"); + assertEquals("get-resource-counts", op.getCode()); + } + + @Test + public void testTransactionFromBundle() throws Exception { + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/transaction_link_patient_eve.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + ourLog.info(response); + } + + @Test + public void testTransactionWithIncompleteBundle() throws Exception { + Patient patient = new Patient(); + patient.setGender(AdministrativeGender.MALE); + + Bundle bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle.addEntry().setResource(patient); + + try { + ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.toString(), containsString("missing or invalid HTTP Verb")); + } + } + + @Test + public void testTransactionFromBundle2() throws Exception { + + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/transaction_link_patient_eve_temp.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + ourLog.info(response); + + Bundle resp = ourCtx.newXmlParser().parseResource(Bundle.class, response); + IdType id1_1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals("Provenance", id1_1.getResourceType()); + IdType id1_2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + IdType id1_3 = new IdType(resp.getEntry().get(2).getResponse().getLocation()); + IdType id1_4 = new IdType(resp.getEntry().get(3).getResponse().getLocation()); + + /* + * Same bundle! + */ + + bundleRes = SystemProviderR4Test.class.getResourceAsStream("/transaction_link_patient_eve_temp.xml"); + bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + response = ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + ourLog.info(response); + + resp = ourCtx.newXmlParser().parseResource(Bundle.class, response); + IdType id2_1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + IdType id2_2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + IdType id2_3 = new IdType(resp.getEntry().get(2).getResponse().getLocation()); + IdType id2_4 = new IdType(resp.getEntry().get(3).getResponse().getLocation()); + + assertNotEquals(id1_1.toVersionless(), id2_1.toVersionless()); + assertEquals("Provenance", id2_1.getResourceType()); + assertEquals(id1_2.toVersionless(), id2_2.toVersionless()); + assertEquals(id1_3.toVersionless(), id2_3.toVersionless()); + assertEquals(id1_4.toVersionless(), id2_4.toVersionless()); + } + + /** + * This is Gramahe's test transaction - it requires some set up in order to work + */ + // @Test + public void testTransactionFromBundle3() throws Exception { + + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/grahame-transaction.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + ourLog.info(response); + } + + @Test + public void testTransactionFromBundle4() throws Exception { + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/simone_bundle.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + ourLog.info(response); + Bundle bundleResp = ourCtx.newXmlParser().parseResource(Bundle.class, response); + IdType id = new IdType(bundleResp.getEntry().get(0).getResponse().getLocation()); + assertEquals("Patient", id.getResourceType()); + assertTrue(id.hasIdPart()); + assertTrue(id.isIdPartValidLong()); + assertTrue(id.hasVersionIdPart()); + assertTrue(id.isVersionIdPartValidLong()); + } + + @Test + public void testTransactionFromBundle5() throws Exception { + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/simone_bundle2.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + try { + ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + fail(); + } catch (InvalidRequestException e) { + OperationOutcome oo = (OperationOutcome) e.getOperationOutcome(); + assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics()); + assertEquals("processing", oo.getIssue().get(0).getCode().toCode()); + } + } + + @Test + public void testTransactionFromBundle6() throws Exception { + InputStream bundleRes = SystemProviderR4Test.class.getResourceAsStream("/simone_bundle3.xml"); + String bundle = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + ourClient.transaction().withBundle(bundle).prettyPrint().execute(); + // try { + // fail(); + // } catch (InvalidRequestException e) { + // OperationOutcome oo = (OperationOutcome) e.getOperationOutcome(); + // assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics()); + // assertEquals("processing", oo.getIssue().get(0).getCode()); + // } + } + + @Test + public void testTransactionSearch() throws Exception { + for (int i = 0; i < 20; i++) { + Patient p = new Patient(); + p.addName().setFamily("PATIENT_" + i); + myPatientDao.create(p, mySrd); + } + + Bundle req = new Bundle(); + req.setType(BundleType.TRANSACTION); + req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?"); + Bundle resp = ourClient.transaction().withBundle(req).execute(); + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(1, resp.getEntry().size()); + Bundle respSub = (Bundle) resp.getEntry().get(0).getResource(); + assertEquals("self", respSub.getLink().get(0).getRelation()); + assertEquals(ourServerBase + "/Patient", respSub.getLink().get(0).getUrl()); + assertEquals("next", respSub.getLink().get(1).getRelation()); + assertThat(respSub.getLink().get(1).getUrl(), containsString("/fhir/context?_getpages")); + assertThat(respSub.getEntry().get(0).getFullUrl(), startsWith(ourServerBase + "/Patient/")); + assertEquals(Patient.class, respSub.getEntry().get(0).getResource().getClass()); + } + + @Test + public void testTransactionCount() throws Exception { + for (int i = 0; i < 20; i++) { + Patient p = new Patient(); + p.addName().setFamily("PATIENT_" + i); + myPatientDao.create(p, mySrd); + } + + Bundle req = new Bundle(); + req.setType(BundleType.TRANSACTION); + req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?_summary=count"); + Bundle resp = ourClient.transaction().withBundle(req).execute(); + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(1, resp.getEntry().size()); + Bundle respSub = (Bundle) resp.getEntry().get(0).getResource(); + assertEquals(20, respSub.getTotal()); + assertEquals(0, respSub.getEntry().size()); + } + + @Test + public void testTransactionCreateWithPreferHeader() throws Exception { + + Patient p = new Patient(); + p.setActive(true); + + Bundle req; + Bundle resp; + + // No prefer header + req = new Bundle(); + req.setType(BundleType.TRANSACTION); + req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + resp = ourClient.transaction().withBundle(req).execute(); + assertEquals(null, resp.getEntry().get(0).getResource()); + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + + // Prefer return=minimal + mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER); + mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_MINIMAL); + req = new Bundle(); + req.setType(BundleType.TRANSACTION); + req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + resp = ourClient.transaction().withBundle(req).execute(); + assertEquals(null, resp.getEntry().get(0).getResource()); + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + + // Prefer return=representation + mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER); + mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION); + req = new Bundle(); + req.setType(BundleType.TRANSACTION); + req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + resp = ourClient.transaction().withBundle(req).execute(); + assertEquals(Patient.class, resp.getEntry().get(0).getResource().getClass()); + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} 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 new file mode 100644 index 00000000000..e7b39e0af25 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java @@ -0,0 +1,312 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +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.Bundle; +import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Patient; +import org.junit.*; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.rp.r4.*; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +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.util.TestUtil; + +public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { + + private static RestfulServer myRestServer; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static CloseableHttpClient ourHttpClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderTransactionSearchR4Test.class); + private static Server ourServer; + private static String ourServerBase; + private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; + + + + @SuppressWarnings("deprecation") + @After + public void after() { + myRestServer.setUseBrowserFriendlyContentTypes(true); + ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); + myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); + } + + @Before + public void before() { + mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); + ourClient.registerInterceptor(mySimpleHeaderInterceptor); + } + + @Before + public void beforeStartServer() throws Exception { + if (myRestServer == null) { + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + + QuestionnaireResourceProviderR4 questionnaireRp = new QuestionnaireResourceProviderR4(); + questionnaireRp.setDao(myQuestionnaireDao); + + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(myObservationDao); + + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(myOrganizationDao); + + RestfulServer restServer = new RestfulServer(ourCtx); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setPlainProviders(mySystemProvider); + + int myPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ourServerBase = "http://localhost:" + myPort + "/fhir/context"; + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourCtx = FhirContext.forR4(); + restServer.setFhirContext(ourCtx); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourHttpClient = builder.build(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.setLogRequestAndResponse(true); + myRestServer = restServer; + } + + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); + } + + + private List create20Patients() { + List ids = new ArrayList(); + for (int i = 0; i < 20; i++) { + Patient patient = new Patient(); + patient.setGender(AdministrativeGender.MALE); + patient.addIdentifier().setSystem("urn:foo").setValue("A"); + patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1)); + String id = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue(); + ids.add(id); + } + return ids; + } + + @Test + public void testBatchWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testBatchWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one batch! Whoa! + */ + @Test + public void testBatchWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + @Test + public void testTransactionWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testTransactionWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one Transaction! Whoa! + */ + @Test + public void testTransactionWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + private List toIds(Bundle theRespBundle) { + ArrayList retVal = new ArrayList(); + for (BundleEntryComponent next : theRespBundle.getEntry()) { + retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + return retVal; + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java new file mode 100644 index 00000000000..708d05d6ac6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java @@ -0,0 +1,209 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.r4.model.Attachment; +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.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderR4Test.class); + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + + @Test + public void testUploadSct() throws Exception { + byte[] packageBytes = createSctZip(); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URL)) + .andParameter("package", new Attachment().setData(packageBytes)) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); + } + + @Test + public void testUploadLoinc() throws Exception { + byte[] packageBytes = createLoincZip(); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URL)) + .andParameter("package", new Attachment().setData(packageBytes)) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); + + /* + * Try uploading a second time + */ + + //@formatter:off + respParam = ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URL)) + .andParameter("package", new Attachment().setData(packageBytes)) + .execute(); + //@formatter:on + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + } + + @Test + public void testUploadSctLocalFile() throws Exception { + byte[] packageBytes = createSctZip(); + File tempFile = File.createTempFile("tmp", ".zip"); + tempFile.deleteOnExit(); + + FileOutputStream fos = new FileOutputStream(tempFile); + fos.write(packageBytes); + fos.close(); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URL)) + .andParameter("localfile", new StringType(tempFile.getAbsolutePath())) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); + } + + @Test + public void testUploadInvalidUrl() throws Exception { + byte[] packageBytes = createSctZip(); + + //@formatter:off + try { + ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URL + "FOO")) + .andParameter("package", new Attachment().setData(packageBytes)) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testUploadMissingUrl() throws Exception { + byte[] packageBytes = createSctZip(); + + //@formatter:off + try { + ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "package", new Attachment().setData(packageBytes)) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testUploadMissingPackage() throws Exception { + //@formatter:off + try { + ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URL)) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage()); + } + //@formatter:on + } + + private byte[] createSctZip() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(bos); + + List inputNames = Arrays.asList("sct2_Concept_Full_INT_20160131.txt","sct2_Concept_Full-en_INT_20160131.txt","sct2_Description_Full-en_INT_20160131.txt","sct2_Identifier_Full_INT_20160131.txt","sct2_Relationship_Full_INT_20160131.txt","sct2_StatedRelationship_Full_INT_20160131.txt","sct2_TextDefinition_Full-en_INT_20160131.txt"); + for (String nextName : inputNames) { + zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + nextName)); + zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName))); + } + zos.close(); + byte[] packageBytes = bos.toByteArray(); + return packageBytes; + } + + private byte[] createLoincZip() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(bos); + + zos.putNextEntry(new ZipEntry("loinc.csv")); + zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/loinc.csv"))); + zos.putNextEntry(new ZipEntry("LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV")); + zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV"))); + zos.close(); + + byte[] packageBytes = bos.toByteArray(); + return packageBytes; + } + +} 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 new file mode 100644 index 00000000000..851f28a3dbf --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java @@ -0,0 +1,97 @@ +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.dao.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; + +public class PagingMultinodeProviderR4Test extends BaseResourceProviderR4Test { + + private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw; + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false); + } + + @Override + public void before() throws Exception { + super.before(); + myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + + myDaoConfig.setAllowMultipleDelete(true); + + mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc); + } + + @Test + public void testSearch() { + { + for (int i = 0; i < 100; i++) { + Patient patient = new Patient(); + String id = "A" + leftPad(Integer.toString(i), 3, '0'); + patient.setId(id); + patient.addIdentifier().setSystem("urn:system").setValue("A" + i); + patient.addName().setFamily(id); + myPatientDao.update(patient, mySrd).getId().toUnqualifiedVersionless(); + } + } + + Bundle found; + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + found = ourClient + .search() + .forResource(Patient.class) + .sort().ascending(Patient.SP_FAMILY) + .count(10) + .returnBundle(Bundle.class) + .execute(); + assertThat(toUnqualifiedVersionlessIdValues(found), contains("Patient/A000", "Patient/A001", "Patient/A002", "Patient/A003", "Patient/A004", "Patient/A005", "Patient/A006", "Patient/A007", "Patient/A008", "Patient/A009")); + + found = ourClient + .loadPage() + .next(found) + .execute(); + assertThat(toUnqualifiedVersionlessIdValues(found), contains("Patient/A010", "Patient/A011", "Patient/A012", "Patient/A013", "Patient/A014", "Patient/A015", "Patient/A016", "Patient/A017", "Patient/A018", "Patient/A019")); + + found = ourClient + .loadPage() + .next(found) + .execute(); + assertThat(toUnqualifiedVersionlessIdValues(found), contains("Patient/A020", "Patient/A021", "Patient/A022", "Patient/A023", "Patient/A024", "Patient/A025", "Patient/A026", "Patient/A027", "Patient/A028", "Patient/A029")); + + found = ourClient + .loadPage() + .next(found) + .execute(); + assertThat(toUnqualifiedVersionlessIdValues(found), contains("Patient/A030", "Patient/A031", "Patient/A032", "Patient/A033", "Patient/A034", "Patient/A035", "Patient/A036", "Patient/A037", "Patient/A038", "Patient/A039")); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java new file mode 100644 index 00000000000..a2b085d7174 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java @@ -0,0 +1,94 @@ +package ca.uhn.fhir.jpa.subscription.r4; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.*; + +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; + +public class FhirR4Util { + + public static final String LPI_CODESYSTEM = "http://cognitivemedicine.com/lpi"; + public static final String LPI_CODE = "LPI-FHIR"; + + public static Subscription createSubscription(String criteria, String payload, String endpoint, IGenericClient client) { + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); + subscription.setCriteria(criteria); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); + channel.setPayload(payload); + channel.setEndpoint(endpoint); + subscription.setChannel(channel); + + MethodOutcome methodOutcome = client.create().resource(subscription).execute(); + subscription.setId(methodOutcome.getId().getIdPart()); + + return subscription; + } + + public static Observation getSnomedObservation() { + Coding snomedCoding = new Coding(); + snomedCoding.setSystem("SNOMED-CT"); + snomedCoding.setCode("1000000050"); + + Observation observation = new Observation(); + + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.getCode().addCoding(snomedCoding); + + return observation; + } + + public static Observation getLoincObservation() { + Coding snomedCoding = new Coding(); + snomedCoding.setSystem("http://loinc.org"); + snomedCoding.setCode("55284-4"); + snomedCoding.setDisplay("Blood Pressure"); + + Observation observation = new Observation(); + + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.getCode().addCoding(snomedCoding); + + return observation; + } + + /** + * Create a patient object for the test + * + * @return + */ + public static Patient getPatient() { + String patientId = "1"; + + Patient patient = new Patient(); + patient.setGender(Enumerations.AdministrativeGender.MALE); + + Identifier identifier = patient.addIdentifier(); + identifier.setValue(patientId); + identifier.setSystem(LPI_CODESYSTEM); + + IBaseMetaType meta = patient.getMeta(); + IBaseCoding tag = meta.addTag(); + tag.setCode(LPI_CODE); + tag.setSystem(LPI_CODESYSTEM); + setTag(patient); + return patient; + } + + /** + * Set the tag for a resource + * + * @param resource + */ + public static void setTag(IBaseResource resource) { + IBaseMetaType meta = resource.getMeta(); + IBaseCoding tag = meta.addTag(); + tag.setCode(LPI_CODE); + tag.setSystem(LPI_CODESYSTEM); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java new file mode 100644 index 00000000000..6adbf4d71ee --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java @@ -0,0 +1,171 @@ +package ca.uhn.fhir.jpa.subscription.r4; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.net.URI; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.hl7.fhir.r4.model.*; +import org.junit.*; +import org.slf4j.Logger; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; + +/** + * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the + * subscription + *

+ * Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdR4Test for + * a test that returns the xml of the observation + *

+ * To execute the following test, execute it the following way: + * 0. execute 'clean' test + * 1. Execute the 'createPatient' test + * 2. Update the patient id static variable + * 3. Execute the 'createSubscription' test + * 4. Update the subscription id static variable + * 5. Execute the 'attachWebSocket' test + * 6. Execute the 'sendObservation' test + * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id + */ +public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Test { + + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaR4Test.class); + + private String myPatientId; + private String mySubscriptionId; + private WebSocketClient myWebSocketClient; + private SocketImplementation mySocketImplementation; + + @Override + @After + public void after() throws Exception { + super.after(); + myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled()); + myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay()); + } + + @Override + @Before + public void before() throws Exception { + super.before(); + + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPollDelay(0L); + + /* + * Create patient + */ + + Patient patient = FhirR4Util.getPatient(); + MethodOutcome methodOutcome = ourClient.create().resource(patient).execute(); + myPatientId = methodOutcome.getId().getIdPart(); + + /* + * Create subscription + */ + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE); + // subscription.setCriteria("Observation?subject=Patient/" + PATIENT_ID); + subscription.setCriteria("Observation?code=SNOMED-CT|82313006&_format=xml"); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); + channel.setPayload("application/json"); + subscription.setChannel(channel); + + methodOutcome = ourClient.create().resource(subscription).execute(); + mySubscriptionId = methodOutcome.getId().getIdPart(); + + /* + * Attach websocket + */ + + myWebSocketClient = new WebSocketClient(); + mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON); + + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + ourLog.info("Connecting to : {}", echoUri); + Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); + Session session = connection.get(2, TimeUnit.SECONDS); + + ourLog.info("Connected to WS: {}", session.isOpen()); + } + + @After + public void afterCloseWebsocket() throws Exception { + ourLog.info("Shutting down websocket client"); + myWebSocketClient.stop(); + } + + @Test + public void createObservation() throws Exception { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode("82313006"); + coding.setSystem("SNOMED-CT"); + Reference reference = new Reference(); + reference.setReference("Patient/" + myPatientId); + observation.setSubject(reference); + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute(); + String observationId = methodOutcome2.getId().getIdPart(); + observation.setId(observationId); + + ourLog.info("Observation id generated by server is: " + observationId); + + int changes = mySubscriptionDao.pollForNewUndeliveredResources(); + ourLog.info("Polling showed {}", changes); + assertEquals(1, changes); + + Thread.sleep(2000); + + ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); + } + + @Test + public void createObservationThatDoesNotMatch() throws Exception { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode("8231"); + coding.setSystem("SNOMED-CT"); + Reference reference = new Reference(); + reference.setReference("Patient/" + myPatientId); + observation.setSubject(reference); + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute(); + String observationId = methodOutcome2.getId().getIdPart(); + observation.setId(observationId); + + ourLog.info("Observation id generated by server is: " + observationId); + + int changes = mySubscriptionDao.pollForNewUndeliveredResources(); + ourLog.info("Polling showed {}", changes); + assertEquals(0, changes); + + Thread.sleep(2000); + + ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java new file mode 100644 index 00000000000..8c93d50ab6f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java @@ -0,0 +1,169 @@ +package ca.uhn.fhir.jpa.subscription.r4; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.net.URI; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.hl7.fhir.r4.model.*; +import org.junit.*; +import org.slf4j.Logger; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; + +/** + * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the + * subscription + *

+ * Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdR4Test for + * a test that returns the xml of the observation + *

+ * To execute the following test, execute it the following way: + * 0. execute 'clean' test + * 1. Execute the 'createPatient' test + * 2. Update the patient id static variable + * 3. Execute the 'createSubscription' test + * 4. Update the subscription id static variable + * 5. Execute the 'attachWebSocket' test + * 6. Execute the 'sendObservation' test + * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id + */ +public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProviderR4Test { + + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdR4Test.class); + + private String myPatientId; + private String mySubscriptionId; + private WebSocketClient myWebSocketClient; + private SocketImplementation mySocketImplementation; + + @After + public void after() throws Exception { + super.after(); + myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled()); + myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay()); + } + + @Before + public void before() throws Exception { + super.before(); + + myDaoConfig.setSubscriptionEnabled(true); + myDaoConfig.setSubscriptionPollDelay(0L); + + /* + * Create patient + */ + + Patient patient = FhirR4Util.getPatient(); + MethodOutcome methodOutcome = ourClient.create().resource(patient).execute(); + myPatientId = methodOutcome.getId().getIdPart(); + + /* + * Create subscription + */ + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE); + // subscription.setCriteria("Observation?subject=Patient/" + PATIENT_ID); + subscription.setCriteria("Observation?code=SNOMED-CT|82313006"); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); + channel.setPayload("application/json"); + subscription.setChannel(channel); + + methodOutcome = ourClient.create().resource(subscription).execute(); + mySubscriptionId = methodOutcome.getId().getIdPart(); + + /* + * Attach websocket + */ + + myWebSocketClient = new WebSocketClient(); + mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON); + + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + ourLog.info("Connecting to : {}", echoUri); + Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); + Session session = connection.get(2, TimeUnit.SECONDS); + + ourLog.info("Connected to WS: {}", session.isOpen()); + } + + @After + public void afterCloseWebsocket() throws Exception { + ourLog.info("Shutting down websocket client"); + myWebSocketClient.stop(); + } + + @Test + public void createObservation() throws Exception { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode("82313006"); + coding.setSystem("SNOMED-CT"); + Reference reference = new Reference(); + reference.setReference("Patient/" + myPatientId); + observation.setSubject(reference); + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute(); + String observationId = methodOutcome2.getId().getIdPart(); + observation.setId(observationId); + + ourLog.info("Observation id generated by server is: " + observationId); + + int changes = mySubscriptionDao.pollForNewUndeliveredResources(); + ourLog.info("Polling showed {}", changes); + assertEquals(1, changes); + + Thread.sleep(2000); + + ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); + } + + @Test + public void createObservationThatDoesNotMatch() throws Exception { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode("8231"); + coding.setSystem("SNOMED-CT"); + Reference reference = new Reference(); + reference.setReference("Patient/" + myPatientId); + observation.setSubject(reference); + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute(); + String observationId = methodOutcome2.getId().getIdPart(); + observation.setId(observationId); + + ourLog.info("Observation id generated by server is: " + observationId); + + int changes = mySubscriptionDao.pollForNewUndeliveredResources(); + ourLog.info("Polling showed {}", changes); + assertEquals(0, changes); + + Thread.sleep(2000); + + ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java new file mode 100644 index 00000000000..02be65c1095 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java @@ -0,0 +1,362 @@ + +package ca.uhn.fhir.jpa.subscription.r4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import ca.uhn.fhir.rest.api.Constants; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.*; + +import com.google.common.collect.Lists; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +/** + * Test the rest-hook subscriptions + */ +public class RestHookTestR4Test extends BaseResourceProviderR4Test { + + private static List ourContentTypes = new ArrayList(); + private static List ourCreatedObservations = Lists.newArrayList(); + private static int ourListenerPort; + private static RestfulServer ourListenerRestServer; + private static Server ourListenerServer; + private static String ourListenerServerBase; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestR4Test.class); + + private static List ourUpdatedObservations = Lists.newArrayList(); + + @After + public void afterUnregisterRestHookListener() { + myDaoConfig.setAllowMultipleDelete(true); + ourLog.info("Deleting all subscriptions"); + ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute(); + ourLog.info("Done deleting all subscriptions"); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + + ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); + } + + @Before + public void beforeRegisterRestHookListener() { + ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); + } + + @Before + public void beforeReset() { + ourCreatedObservations.clear(); + ourUpdatedObservations.clear(); + ourContentTypes.clear(); + } + + @Test + public void testRestHookSubscriptionInvalidCriteria() throws Exception { + String payload = "application/xml"; + + String criteria1 = "Observation?codeeeee=SNOMED-CT"; + + try { + createSubscription(criteria1, payload, ourListenerServerBase); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Invalid criteria: Failed to parse match URL[Observation?codeeeee=SNOMED-CT] - Resource type Observation does not have a parameter with name: codeeeee", e.getMessage()); + } + } + + + private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) { + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE); + subscription.setCriteria(theCriteria); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); + channel.setPayload(thePayload); + channel.setEndpoint(theEndpoint); + subscription.setChannel(channel); + + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + subscription.setId(methodOutcome.getId().getIdPart()); + + return subscription; + } + + private Observation sendObservation(String code, String system) { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code); + coding.setSystem(system); + + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); + + String observationId = methodOutcome.getId().getIdPart(); + observation.setId(observationId); + + return observation; + } + + @Test + public void testRestHookSubscriptionApplicationFhirJson() throws Exception { + String payload = "application/fhir+json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + createSubscription(criteria1, payload, ourListenerServerBase); + createSubscription(criteria2, payload, ourListenerServerBase); + + sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + } + + @Test + public void testRestHookSubscriptionApplicationJson() throws Exception { + String payload = "application/json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + + Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId()); + Assert.assertNotNull(subscriptionTemp); + + subscriptionTemp.setCriteria(criteria1); + ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); + + + Observation observation2 = sendObservation(code, "SNOMED-CT"); + + // Should see two subscription notifications + Thread.sleep(500); + assertEquals(3, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + ourClient.delete().resourceById(new IdDt("Subscription", subscription2.getId())).execute(); + + Observation observationTemp3 = sendObservation(code, "SNOMED-CT"); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId()); + CodeableConcept codeableConcept = new CodeableConcept(); + observation3.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code + "111"); + coding.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute(); + + // Should see no subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId()); + + CodeableConcept codeableConcept1 = new CodeableConcept(); + observation3a.setCode(codeableConcept1); + Coding coding1 = codeableConcept1.addCoding(); + coding1.setCode(code); + coding1.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute(); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(1, ourUpdatedObservations.size()); + + Assert.assertFalse(subscription1.getId().equals(subscription2.getId())); + Assert.assertFalse(observation1.getId().isEmpty()); + Assert.assertFalse(observation2.getId().isEmpty()); + } + + @Test + public void testRestHookSubscriptionApplicationXmlJson() throws Exception { + String payload = "application/fhir+xml"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0)); + } + + + @Test + public void testRestHookSubscriptionApplicationXml() throws Exception { + String payload = "application/xml"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0)); + + Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId()); + Assert.assertNotNull(subscriptionTemp); + + subscriptionTemp.setCriteria(criteria1); + ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); + + + Observation observation2 = sendObservation(code, "SNOMED-CT"); + + // Should see two subscription notifications + Thread.sleep(500); + assertEquals(3, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + ourClient.delete().resourceById(new IdDt("Subscription", subscription2.getId())).execute(); + + Observation observationTemp3 = sendObservation(code, "SNOMED-CT"); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId()); + CodeableConcept codeableConcept = new CodeableConcept(); + observation3.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code + "111"); + coding.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute(); + + // Should see no subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId()); + + CodeableConcept codeableConcept1 = new CodeableConcept(); + observation3a.setCode(codeableConcept1); + Coding coding1 = codeableConcept1.addCoding(); + coding1.setCode(code); + coding1.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute(); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(1, ourUpdatedObservations.size()); + + Assert.assertFalse(subscription1.getId().equals(subscription2.getId())); + Assert.assertFalse(observation1.getId().isEmpty()); + Assert.assertFalse(observation2.getId().isEmpty()); + } + + @BeforeClass + public static void startListenerServer() throws Exception { + ourListenerPort = RandomServerPortProvider.findFreePort(); + ourListenerRestServer = new RestfulServer(FhirContext.forR4()); + ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context"; + + ObservationListener obsListener = new ObservationListener(); + ourListenerRestServer.setResourceProviders(obsListener); + + ourListenerServer = new Server(ourListenerPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(ourListenerRestServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourListenerServer.setHandler(proxyHandler); + ourListenerServer.start(); + } + + @AfterClass + public static void stopListenerServer() throws Exception { + ourListenerServer.stop(); + } + + public static class ObservationListener implements IResourceProvider { + + @Create + public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { + ourLog.info("Received Listener Create"); + ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); + ourCreatedObservations.add(theObservation); + return new MethodOutcome(new IdType("Observation/1"), true); + } + + @Override + public Class getResourceType() { + return Observation.class; + } + + @Update + public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { + ourLog.info("Received Listener Update"); + ourUpdatedObservations.add(theObservation); + ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); + return new MethodOutcome(new IdType("Observation/1"), false); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java new file mode 100644 index 00000000000..43ccafbf509 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java @@ -0,0 +1,299 @@ + +package ca.uhn.fhir.jpa.subscription.r4; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.*; + +import com.google.common.collect.Lists; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; + +/** + * Test the rest-hook subscriptions + */ +public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends BaseResourceProviderR4Test { + + private static List ourCreatedObservations = Lists.newArrayList(); + private static int ourListenerPort; + private static RestfulServer ourListenerRestServer; + private static Server ourListenerServer; + private static String ourListenerServerBase; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class); + private static List ourUpdatedObservations = Lists.newArrayList(); + + @Override + protected boolean shouldLogClient() { + return false; + } + + @After + public void afterUnregisterRestHookListener() { + myDaoConfig.setAllowMultipleDelete(true); + ourLog.info("Deleting all subscriptions"); + ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute(); + ourLog.info("Done deleting all subscriptions"); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + + myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor); + } + + @Before + public void beforeRegisterRestHookListener() { + myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor); + } + + @Before + public void beforeReset() { + ourCreatedObservations.clear(); + ourUpdatedObservations.clear(); + } + + private Subscription createSubscription(String criteria, String payload, String endpoint) { + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE); + subscription.setCriteria(criteria); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); + channel.setPayload(payload); + channel.setEndpoint(endpoint); + subscription.setChannel(channel); + + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + subscription.setId(methodOutcome.getId().getIdPart()); + + return subscription; + } + + private Observation sendObservation(String code, String system) { + Observation observation = new Observation(); + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code); + coding.setSystem(system); + + observation.setStatus(Observation.ObservationStatus.FINAL); + + MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); + + String observationId = methodOutcome.getId().getIdPart(); + observation.setId(observationId); + + return observation; + } + + @Test + public void testRestHookSubscriptionJson() throws Exception { + String payload = "application/json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId()); + Assert.assertNotNull(subscriptionTemp); + + subscriptionTemp.setCriteria(criteria1); + ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); + + + Observation observation2 = sendObservation(code, "SNOMED-CT"); + + // Should see two subscription notifications + Thread.sleep(500); + assertEquals(3, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + ourClient.delete().resourceById(new IdDt("Subscription", subscription2.getId())).execute(); + + Observation observationTemp3 = sendObservation(code, "SNOMED-CT"); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId()); + CodeableConcept codeableConcept = new CodeableConcept(); + observation3.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code + "111"); + coding.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute(); + + // Should see no subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId()); + + CodeableConcept codeableConcept1 = new CodeableConcept(); + observation3a.setCode(codeableConcept1); + Coding coding1 = codeableConcept1.addCoding(); + coding1.setCode(code); + coding1.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute(); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(1, ourUpdatedObservations.size()); + + Assert.assertFalse(subscription1.getId().equals(subscription2.getId())); + Assert.assertFalse(observation1.getId().isEmpty()); + Assert.assertFalse(observation2.getId().isEmpty()); + } + + @Test + public void testRestHookSubscriptionXml() throws Exception { + String payload = "application/xml"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + Thread.sleep(500); + assertEquals(1, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId()); + Assert.assertNotNull(subscriptionTemp); + + subscriptionTemp.setCriteria(criteria1); + ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); + + + Observation observation2 = sendObservation(code, "SNOMED-CT"); + + // Should see two subscription notifications + Thread.sleep(500); + assertEquals(3, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + ourClient.delete().resourceById(new IdDt("Subscription", subscription2.getId())).execute(); + + Observation observationTemp3 = sendObservation(code, "SNOMED-CT"); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId()); + CodeableConcept codeableConcept = new CodeableConcept(); + observation3.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code + "111"); + coding.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute(); + + // Should see no subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(0, ourUpdatedObservations.size()); + + Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId()); + + CodeableConcept codeableConcept1 = new CodeableConcept(); + observation3a.setCode(codeableConcept1); + Coding coding1 = codeableConcept1.addCoding(); + coding1.setCode(code); + coding1.setSystem("SNOMED-CT"); + ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute(); + + // Should see only one subscription notification + Thread.sleep(500); + assertEquals(4, ourCreatedObservations.size()); + assertEquals(1, ourUpdatedObservations.size()); + + Assert.assertFalse(subscription1.getId().equals(subscription2.getId())); + Assert.assertFalse(observation1.getId().isEmpty()); + Assert.assertFalse(observation2.getId().isEmpty()); + } + + @BeforeClass + public static void startListenerServer() throws Exception { + ourListenerPort = RandomServerPortProvider.findFreePort(); + ourListenerRestServer = new RestfulServer(FhirContext.forR4()); + ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context"; + + ObservationListener obsListener = new ObservationListener(); + ourListenerRestServer.setResourceProviders(obsListener); + + ourListenerServer = new Server(ourListenerPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(ourListenerRestServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourListenerServer.setHandler(proxyHandler); + ourListenerServer.start(); + } + + @AfterClass + public static void stopListenerServer() throws Exception { + ourListenerServer.stop(); + } + + public static class ObservationListener implements IResourceProvider { + + @Create + public MethodOutcome create(@ResourceParam Observation theObservation) { + ourLog.info("Received Listener Create"); + ourCreatedObservations.add(theObservation); + return new MethodOutcome(new IdType("Observation/1"), true); + } + + @Override + public Class getResourceType() { + return Observation.class; + } + + @Update + public MethodOutcome update(@ResourceParam Observation theObservation) { + ourLog.info("Received Listener Update"); + ourUpdatedObservations.add(theObservation); + return new MethodOutcome(new IdType("Observation/1"), false); + } + + } + +} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/ObjectConverter.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/ObjectConverter.java index 19c34f013a2..e7a5f9d125b 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/ObjectConverter.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/ObjectConverter.java @@ -4,6 +4,9 @@ import java.io.*; import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.r4.elementmodel.Element; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.exceptions.*; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.context.IWorkerContext; @@ -109,4 +112,22 @@ public class ObjectConverter { return c; } + public static Identifier readAsIdentifier(Element item) { + Identifier r = new Identifier(); + r.setSystem(item.getNamedChildValue("system")); + r.setValue(item.getNamedChildValue("value")); + return r; + } + + public static Reference readAsReference(Element item) { + Reference r = new Reference(); + r.setDisplay(item.getNamedChildValue("display")); + r.setReference(item.getNamedChildValue("reference")); + List identifier = item.getChildrenByName("identifier"); + if (identifier.isEmpty() == false) { + r.setIdentifier(readAsIdentifier(identifier.get(0))); + } + return r; + } + } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java index 226863f51e8..693fc77ea18 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java @@ -173,7 +173,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv retVal.setPublisher(myPublisher); retVal.setDateElement(conformanceDate()); - retVal.setFhirVersion(FhirVersionEnum.DSTU3.getFhirVersionString()); + retVal.setFhirVersion(FhirVersionEnum.R4.getFhirVersionString()); retVal.setAcceptUnknown(UnknownContentCode.EXTENSIONS); // TODO: make this configurable - this is a fairly big // effort since the parser // needs to be modified to actually allow it diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java index fccd8238a3a..297332126f7 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java @@ -2,16 +2,14 @@ package org.hl7.fhir.r4.model; import static org.apache.commons.lang3.StringUtils.isBlank; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; +import java.util.*; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.parser.DataFormatException; public abstract class BaseDateTimeType extends PrimitiveType { diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateTimeType.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateTimeType.java index 98f72dfa9a3..1a3721371af 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateTimeType.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateTimeType.java @@ -29,13 +29,12 @@ POSSIBILITY OF SUCH DAMAGE. package org.hl7.fhir.r4.model; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; +import java.util.*; import java.util.zip.DataFormatException; import org.apache.commons.lang3.time.DateUtils; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateType.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateType.java index 05f46f3573d..65d5cf6ffba 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateType.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/DateType.java @@ -29,18 +29,14 @@ POSSIBILITY OF SUCH DAMAGE. package org.hl7.fhir.r4.model; -import java.util.Calendar; - /** * Primitive type "date" in FHIR: any day in a gregorian calendar */ - -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; +import java.util.*; import org.apache.commons.lang3.Validate; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/InstantType.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/InstantType.java index 054822de639..0dcfe50383c 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/InstantType.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/InstantType.java @@ -31,11 +31,10 @@ POSSIBILITY OF SUCH DAMAGE. */ package org.hl7.fhir.r4.model; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; +import java.util.*; import java.util.zip.DataFormatException; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/Period.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/Period.java index b3e362319bf..d309bb5766b 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/Period.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/Period.java @@ -30,16 +30,14 @@ package org.hl7.fhir.r4.model; */ // Generated on Sat, Jul 8, 2017 23:19+1000 for FHIR v3.1.0 +import java.util.Date; +import java.util.List; -import java.util.*; - -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.ChildOrder; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.api.annotation.DatatypeDef; -import ca.uhn.fhir.model.api.annotation.Block; -import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.ICompositeType; + +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.api.annotation.*; /** * A time period defined by a start and end date and optionally time. */ diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/TemporalPrecisionEnum.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/TemporalPrecisionEnum.java deleted file mode 100644 index f766f6ee510..00000000000 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/TemporalPrecisionEnum.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.hl7.fhir.r4.model; - -import java.util.Calendar; -import java.util.Date; - -import org.apache.commons.lang3.time.DateUtils; - -public enum TemporalPrecisionEnum { - - YEAR(Calendar.YEAR) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addYears(theInput, theAmount); - } - }, - - MONTH(Calendar.MONTH) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMonths(theInput, theAmount); - } - }, - DAY(Calendar.DATE) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addDays(theInput, theAmount); - } - }, - MINUTE(Calendar.MINUTE) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMinutes(theInput, theAmount); - } - }, - SECOND(Calendar.SECOND) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addSeconds(theInput, theAmount); - } - }, - MILLI(Calendar.MILLISECOND) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMilliseconds(theInput, theAmount); - } - }, - - ; - - private int myCalendarConstant; - - TemporalPrecisionEnum(int theCalendarConstant) { - myCalendarConstant = theCalendarConstant; - } - - public abstract Date add(Date theInput, int theAmount); - - public int getCalendarConstant() { - return myCalendarConstant; - } - -} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/terminologies/ValueSetExpanderSimple.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/terminologies/ValueSetExpanderSimple.java index e9981f9ca33..7f96f570e56 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/terminologies/ValueSetExpanderSimple.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/terminologies/ValueSetExpanderSimple.java @@ -166,7 +166,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander { private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, ExpansionProfile profile, List filters) throws FHIRException { - def.checkNoModifiers("Code in Code System", "expanding"); +// def.checkNoModifiers("Code in Code System", "expanding"); if (!CodeSystemUtilities.isDeprecated(cs, def)) { ValueSetExpansionContainsComponent np = null; boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java index 52eea610b67..edceebf132f 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java @@ -1,54 +1,23 @@ package org.hl7.fhir.r4.utils; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import org.hl7.fhir.exceptions.*; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.elementmodel.Element; -import org.hl7.fhir.r4.elementmodel.ObjectConverter; -import org.hl7.fhir.r4.model.Base; -import org.hl7.fhir.r4.model.BooleanType; -import org.hl7.fhir.r4.model.Coding; -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.ElementDefinition; +import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r4.model.ExpressionNode; -import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus; -import org.hl7.fhir.r4.model.ExpressionNode.Function; -import org.hl7.fhir.r4.model.ExpressionNode.Kind; -import org.hl7.fhir.r4.model.ExpressionNode.Operation; -import org.hl7.fhir.r4.model.ExpressionNode.SourceLocation; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.Property; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ExpressionNode.*; import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; -import org.hl7.fhir.r4.model.TemporalPrecisionEnum; -import org.hl7.fhir.r4.model.TimeType; -import org.hl7.fhir.r4.model.TypeDetails; -import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.PathEngineException; -import org.hl7.fhir.exceptions.UcumException; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.ucum.Decimal; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ElementUtil; diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java index 65b6f7775fd..ace0ee3f121 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java @@ -149,7 +149,7 @@ public class ClientR4Test { HttpPost post = (HttpPost) capt.getValue(); assertThat(IOUtils.toString(post.getEntity().getContent(), Charsets.UTF_8), StringContains.containsString(" capt = ArgumentCaptor.forClass(HttpUriRequest.class); + + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + + myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); + myCtx.newRestfulGenericClient("http://foo").forceConformanceCheck(); + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index f01846ab041..5c13093d826 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -458,13 +458,6 @@ public class GenericClientTest { lm.setTimeZoneZulu(true); assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString()); - List tags = response.getMeta().getTag(); - assertNotNull(tags); - assertEquals(1, tags.size()); - assertEquals("http://hl7.org/fhir/tag", tags.get(0).getSystem()); - assertEquals("http://foo/tagdefinition.html", tags.get(0).getCode()); - assertEquals("Some tag", tags.get(0).getDisplay()); - } @Test diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java index 9bbb6284434..14e3e182354 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java @@ -81,7 +81,7 @@ public class LoggingInterceptorTest { public boolean matches(final Object argument) { String formattedMessage = ((LoggingEvent) argument).getFormattedMessage(); System.out.println("Verifying: " + formattedMessage); - return formattedMessage.replace("; ", ";").toLowerCase().contains("Content-Type: application/xml+fhir;charset=utf-8".toLowerCase()); + return formattedMessage.replace("; ", ";").toLowerCase().contains("Content-Type: application/fhir+xml;charset=utf-8".toLowerCase()); } })); @@ -96,7 +96,7 @@ public class LoggingInterceptorTest { public boolean matches(final Object argument) { String formattedMessage = ((LoggingEvent) argument).getFormattedMessage(); System.out.println("Verifying: " + formattedMessage); - return formattedMessage.replace("; ", ";").toLowerCase().contains("Content-Type: application/xml+fhir;charset=utf-8".toLowerCase()); + return formattedMessage.replace("; ", ";").toLowerCase().contains("Content-Type: application/fhir+xml;charset=utf-8".toLowerCase()); } })); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientDstu1Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientDstu1Test.java deleted file mode 100644 index dba31f501d7..00000000000 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientDstu1Test.java +++ /dev/null @@ -1,130 +0,0 @@ -package ca.uhn.fhir.rest.client; - -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.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.StringReader; -import java.nio.charset.Charset; -import java.util.*; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.ReaderInputStream; -import org.apache.http.HttpResponse; -import org.apache.http.ProtocolVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicStatusLine; -import org.hamcrest.Matchers; -import org.hl7.fhir.r4.model.Encounter; -import org.junit.*; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.client.api.IBasicClient; -import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.param.TokenOrListParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.util.TestUtil; - -public class SearchClientDstu1Test { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchClientDstu1Test.class); - - private FhirContext ourCtx; - private HttpClient ourHttpClient; - private HttpResponse ourHttpResponse; - - @Before - public void before() { - ourCtx = FhirContext.forR4(); - - ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); - ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient); - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - - ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); - } - - @Test - public void testPostOnLongParamsList() throws Exception { - String retVal = "<id>bc59fca7-0a8f-4caf-abef-45c8d53ece6a</id><link rel=\"self\" href=\"http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Encounter?identifier=urn%3Aoid%3A1.3.6.1.4.1.12201.2%7C11410000159&_include=Encounter.participant&_include=Encounter.location.location&_include=Encounter.subject\"/><link rel=\"fhir-base\" href=\"http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults><author><name>HAPI FHIR Server</name></author>" - + "<entry><title>Encounter 5994268http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Encounter/59942682014-08-05T12:00:11.000-04:002014-08-05T11:59:21.000-04:00

No narrative template available for resource profile: http://fhir.connectinggta.ca/Profile/encounter
" - + "Patient 5993715http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Patient/59937152014-08-08T14:46:16-04:00
Person CHA
IdentifierUHN MRN 7018614
Address100 Dundas street west
Toronto ON Can
Date of birth01 January 1988
" - + "Practitioner Practitioner/5738815http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Practitioner/57388152014-08-08T13:53:52.000-04:002009-12-04T13:43:11.000-05:00
No narrative template available for resource profile: http://hl7.org/fhir/profiles/Practitioner
" - + "Location Location/5994269http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Location/59942692014-08-08T14:46:16-04:00
No narrative template available for resource profile: http://hl7.org/fhir/profiles/Location
"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse); - when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8")); - when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"))); - - ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo"); - Set includes = new HashSet(); - includes.add(new Include("one")); - includes.add(new Include("two")); - TokenOrListParam params = new TokenOrListParam(); - for (int i = 0; i < 1000; i++) { - params.add(new TokenParam("system", "value")); - } - List found = client.searchByList(params, includes); - - assertEquals(1, found.size()); - - Encounter encounter = found.get(0); - assertNotNull(encounter.getSubject().getResource()); - HttpUriRequest value = capt.getValue(); - - assertTrue("Expected request of type POST on long params list", value instanceof HttpPost); - HttpPost post = (HttpPost) value; - String body = IOUtils.toString(post.getEntity().getContent()); - ourLog.info(body); - assertThat(body, Matchers.containsString("_include=one")); - assertThat(body, Matchers.containsString("_include=two")); - } - - @Test - public void testReturnTypedList() throws Exception { - String retVal = "<id>bc59fca7-0a8f-4caf-abef-45c8d53ece6a</id><link rel=\"self\" href=\"http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Encounter?identifier=urn%3Aoid%3A1.3.6.1.4.1.12201.2%7C11410000159&_include=Encounter.participant&_include=Encounter.location.location&_include=Encounter.subject\"/><link rel=\"fhir-base\" href=\"http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults><author><name>HAPI FHIR Server</name></author><entry><title>Encounter 5994268http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Encounter/59942682014-08-05T12:00:11.000-04:002014-08-05T11:59:21.000-04:00
No narrative template available for resource profile: http://fhir.connectinggta.ca/Profile/encounter
Patient 5993715http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Patient/59937152014-08-08T14:46:16-04:00
Person CHA
IdentifierUHN MRN 7018614
Address100 Dundas street west
Toronto ON Can
Date of birth01 January 1988
Practitioner Practitioner/5738815http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Practitioner/57388152014-08-08T13:53:52.000-04:002009-12-04T13:43:11.000-05:00
No narrative template available for resource profile: http://hl7.org/fhir/profiles/Practitioner
Location Location/5994269http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/Location/59942692014-08-08T14:46:16-04:00
No narrative template available for resource profile: http://hl7.org/fhir/profiles/Location
"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse); - when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8")); - when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"))); - - ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo"); - List found = client.search(); - assertEquals(1, found.size()); - - Encounter encounter = found.get(0); - assertNotNull(encounter.getSubject().getResource()); - } - - private interface ITestClient extends IBasicClient { - - @Search - List search(); - - @Search - List searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenOrListParam tokenOrListParam, @IncludeParam Set theIncludes) throws BaseServerResponseException; - - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - -} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientTest.java new file mode 100644 index 00000000000..37375c04f9c --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SearchClientTest.java @@ -0,0 +1,140 @@ +package ca.uhn.fhir.rest.client; + +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.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.*; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hamcrest.Matchers; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Encounter; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.util.TestUtil; + +public class SearchClientTest { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchClientTest.class); + + private FhirContext ourCtx; + private HttpClient ourHttpClient; + private HttpResponse ourHttpResponse; + + @Before + public void before() { + ourCtx = FhirContext.forR4(); + + ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + + ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + } + + @Test + public void testPostOnLongParamsList() throws Exception { + String resp = createBundle(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse); + when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8")); + when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8"))); + + ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo"); + Set includes = new HashSet(); + includes.add(new Include("one")); + includes.add(new Include("two")); + TokenOrListParam params = new TokenOrListParam(); + for (int i = 0; i < 1000; i++) { + params.add(new TokenParam("system", "value")); + } + List found = client.searchByList(params, includes); + + assertEquals(1, found.size()); + + Encounter encounter = found.get(0); + assertNotNull(encounter.getSubject().getReference()); + HttpUriRequest value = capt.getValue(); + + assertTrue("Expected request of type POST on long params list", value instanceof HttpPost); + HttpPost post = (HttpPost) value; + String body = IOUtils.toString(post.getEntity().getContent()); + ourLog.info(body); + assertThat(body, Matchers.containsString("_include=one")); + assertThat(body, Matchers.containsString("_include=two")); + } + + @Test + public void testReturnTypedList() throws Exception { + + String resp = createBundle(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse); + when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8")); + when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8"))); + + ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo"); + List found = client.search(); + assertEquals(1, found.size()); + + Encounter encounter = found.get(0); + assertNotNull(encounter.getSubject().getReference()); + } + + private String createBundle() { + Bundle bundle = new Bundle(); + + Encounter enc = new Encounter(); + enc.getSubject().setReference("Patient/1"); + + bundle.addEntry().setResource(enc); + + String retVal = ourCtx.newXmlParser().encodeResourceToString(bundle); + return retVal; + } + + private interface ITestClient extends IBasicClient { + + @Search + List search(); + + @Search + List searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenOrListParam tokenOrListParam, @IncludeParam Set theIncludes) throws BaseServerResponseException; + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SortClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SortClientTest.java index e558f69d272..1acdaf34b94 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SortClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/SortClientTest.java @@ -80,7 +80,7 @@ public class SortClientTest { assertEquals(HttpGet.class, capt.getValue().getClass()); HttpGet get = (HttpGet) capt.getValue(); - assertEquals("http://foo/Patient?name=hello&_sort%3Adesc=given&_sort%3Aasc=family", get.getURI().toString()); + assertEquals("http://foo/Patient?name=hello&_sort=-given%2Cfamily", get.getURI().toString()); } private String createBundle() { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java index ca48c155139..87c27d7362d 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java @@ -66,9 +66,9 @@ public class IncludeTest { assertEquals(3, bundle.getEntry().size()); - assertEquals(new IdType("Patient/p1"), bundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); - assertEquals(new IdType("Patient/p2"), bundle.getEntry().get(1).getResource().getIdElement().toUnqualifiedVersionless().getValue()); - assertEquals(new IdType("Organization/o1"), bundle.getEntry().get(2).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertEquals(("Patient/p1"), bundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertEquals(("Patient/p2"), bundle.getEntry().get(1).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertEquals(("Organization/o1"), bundle.getEntry().get(2).getResource().getIdElement().toUnqualifiedVersionless().getValue()); Patient p1 = (Patient) bundle.getEntry().get(0).getResource(); assertEquals(0, p1.getContained().size()); diff --git a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java index e60e14d91e8..17b7f985150 100644 --- a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java +++ b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java @@ -31,7 +31,7 @@ public class PropertyTest { @Before public void setUp() throws IOException { - final String sdString = IOUtils.toString(getClass().getResourceAsStream("/customPatientSd.xml"), StandardCharsets.UTF_8); + final String sdString = IOUtils.toString(PropertyTest.class.getResourceAsStream("/customPatientSd.xml"), StandardCharsets.UTF_8); final IParser parser = ourCtx.newXmlParser(); sd = parser.parseResource(StructureDefinition.class, sdString); workerContext = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport()); diff --git a/hapi-fhir-structures-r4/src/test/resource/customPatientSd.xml b/hapi-fhir-structures-r4/src/test/resources/customPatientSd.xml similarity index 100% rename from hapi-fhir-structures-r4/src/test/resource/customPatientSd.xml rename to hapi-fhir-structures-r4/src/test/resources/customPatientSd.xml diff --git a/hapi-fhir-structures-dstu/src/test/resources/logback-test-dstuforce.xml b/hapi-fhir-structures-r4/src/test/resources/logback-test-dstuforce.xml similarity index 100% rename from hapi-fhir-structures-dstu/src/test/resources/logback-test-dstuforce.xml rename to hapi-fhir-structures-r4/src/test/resources/logback-test-dstuforce.xml diff --git a/hapi-fhir-structures-r4/src/test/resource/logback-test.xml b/hapi-fhir-structures-r4/src/test/resources/logback-test.xml similarity index 100% rename from hapi-fhir-structures-r4/src/test/resource/logback-test.xml rename to hapi-fhir-structures-r4/src/test/resources/logback-test.xml diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java index 4f15f34eda0..1f6aaac352c 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.util.*; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.exceptions.*; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.context.IWorkerContext; @@ -333,15 +334,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat source = Source.InstanceValidator; } - public InstanceValidator(ValidationEngine engine) { - super(); - this.context = engine.getContext(); - fpe = engine.getFpe(); - this.externalHostServices = fpe.getHostServices(); - fpe.setHostServices(new ValidatorHostServices()); - source = Source.InstanceValidator; - } - @Override public boolean isNoInvariantChecks() { return noInvariantChecks; @@ -1427,16 +1419,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private void checkReference(ValidatorHostContext hostContext, List errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException, IOException { - String ref = null; - try { - // Do this inside a try because invalid instances might provide more than one reference. - ref = element.getNamedChildValue("reference"); - } catch (Error e) { - - } + Reference reference = ObjectConverter.readAsReference(element); + + String ref = reference.getReference(); if (Utilities.noString(ref)) { - // todo - what should we do in this case? - warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference should have a display"); + if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) { + warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference or identifier should have a display"); + } return; } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/ValidationEngine.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/ValidationEngine.java deleted file mode 100644 index 46bf6024d42..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/ValidationEngine.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.hl7.fhir.r4.validation; - -import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.utils.FHIRPathEngine; - -/** - * Placeholdr class only, do not use - */ -public class ValidationEngine { - - public IWorkerContext getContext() { - // TODO Auto-generated method stub - return null; - } - - public FHIRPathEngine getFpe() { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorR4Test.java index f16271d19f4..1d4d1e28ae5 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorR4Test.java @@ -43,7 +43,7 @@ import ca.uhn.fhir.util.TestUtil; public class FhirInstanceValidatorR4Test { private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); - private static FhirContext ourCtx = FhirContext.forDstu3(); + private static FhirContext ourCtx = FhirContext.forR4(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorR4Test.class); private FhirInstanceValidator myInstanceVal; private IValidationSupport myMockSupport; @@ -157,20 +157,6 @@ public class FhirInstanceValidatorR4Test { ourLog.info("Validated the following:\n{}", ids); } - /** - * FHIRPathEngine was throwing Error... - */ - @Test - public void testValidateCrucibleCarePlan() throws Exception { - org.hl7.fhir.r4.model.Bundle bundle; - String name = "profiles-resources"; - ourLog.info("Uploading " + name); - String vsContents; - vsContents = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/crucible-condition.xml"), "UTF-8"); - - ValidationResult output = myVal.validateWithResult(vsContents); - List errors = logResultsAndReturnNonInformationalOnes(output); - } @Test @Ignore @@ -201,7 +187,7 @@ public class FhirInstanceValidatorR4Test { } - @Test + @Test @Ignore public void testValidateDocument() throws Exception { String vsContents = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/sample-document.xml"), "UTF-8"); @@ -367,6 +353,7 @@ public class FhirInstanceValidatorR4Test { } @Test + @Ignore public void testValidateBigRawJsonResource() throws Exception { InputStream stream = FhirInstanceValidatorR4Test.class.getResourceAsStream("/conformance.json.gz"); stream = new GZIPInputStream(stream); @@ -389,6 +376,7 @@ public class FhirInstanceValidatorR4Test { } @Test + @Ignore public void testValidateQuestionnaireResponse() throws IOException { String input = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/qr_jon.xml")); @@ -520,6 +508,7 @@ public class FhirInstanceValidatorR4Test { } @Test + @Ignore public void testValidateRawJsonResourceFromExamples() throws Exception { // @formatter:off String input = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream("/testscript-search.json")); @@ -598,7 +587,7 @@ public class FhirInstanceValidatorR4Test { ourLog.info(output.getMessages().get(0).getLocationString()); ourLog.info(output.getMessages().get(0).getMessage()); assertEquals("/f:Patient", output.getMessages().get(0).getLocationString()); - assertEquals("Undefined element 'foo\"", output.getMessages().get(0).getMessage()); + assertEquals("Undefined element 'foo'", output.getMessages().get(0).getMessage()); } @Test 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 d3c5c30e8c8..cb9cee934cc 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 @@ -52,8 +52,12 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp IFhirResourceDaoValueSet #elseif ( ${versionCapitalized} == 'Dstu3' && ${res.name} == 'ValueSet' ) IFhirResourceDaoValueSet +#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ValueSet' ) + IFhirResourceDaoValueSet #elseif ( ${versionCapitalized} == 'Dstu3' && ${res.name} == 'CodeSystem' ) IFhirResourceDaoCodeSystem +#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'CodeSystem' ) + IFhirResourceDaoCodeSystem #elseif ( ${versionCapitalized} != 'Dstu1' && ( ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter')) IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}> #else