diff --git a/pom.xml b/pom.xml index 7cc8094..8b61b9b 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.0.0-SNAPSHOT + 5.1.0-SNAPSHOT hapi-fhir-jpaserver-starter @@ -255,7 +255,12 @@ test - + + org.awaitility + awaitility + 4.0.0-rc1 + test + war diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/EmpiConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/EmpiConfig.java index 3617036..c34f3e1 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/EmpiConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/EmpiConfig.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.empi.api.IEmpiSettings; -import ca.uhn.fhir.empi.rules.config.EmpiSettingsImpl; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig; import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig; import com.google.common.base.Charsets; @@ -22,6 +22,6 @@ public class EmpiConfig { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource("empi-rules.json"); String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); - return new EmpiSettingsImpl().setEnabled(HapiProperties.getEmpiEnabled()).setScriptText(json); + return new EmpiSettings().setEnabled(HapiProperties.getEmpiEnabled()).setScriptText(json); } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java index c36c668..df39a4c 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java @@ -201,11 +201,10 @@ public class FhirServerConfigCommon { retVal.setSmtpServerPort(this.emailPort); retVal.setSmtpServerUsername(this.emailUsername); retVal.setSmtpServerPassword(this.emailPassword); - // FIXME KHS add these once this has merged in hapi -// retVal.setAuth(this.emailAuth); -// retVal.setStartTlsEnable(this.emailStartTlsEnable); -// retVal.setStartTlsRequired(this.emailStartTlsRequired); -// retVal.setQuitWait(this.emailQuitWait); + retVal.setAuth(this.emailAuth); + retVal.setStartTlsEnable(this.emailStartTlsEnable); + retVal.setStartTlsRequired(this.emailStartTlsRequired); + retVal.setQuitWait(this.emailQuitWait); Validate.notNull(mySubscriptionDeliveryHandlerFactory, "No subscription delivery handler"); mySubscriptionDeliveryHandlerFactory.setEmailSender(retVal); diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java index a159758..c0fe97c 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java @@ -58,6 +58,10 @@ public class HapiProperties { static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled"; static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled"; static final String EMPI_ENABLED = "empi.enabled"; + static final String PARTITIONING_ENABLED = "partitioning.enabled"; + static final String PARTITIONING_CROSS_PARTITION_REFERENCE_MODE = "partitioning.cross_partition_reference_mode"; + private static final String PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES = "partitioning.partitioning_include_in_search_hashes"; + static final String ALLOWED_BUNDLE_TYPES = "allowed_bundle_types"; static final String TEST_PORT = "test.port"; static final String TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS = "tester.config.refuse_to_fetch_third_party_urls"; @@ -378,6 +382,18 @@ public class HapiProperties { return HapiProperties.getBooleanProperty(EMPI_ENABLED, false); } + public static Boolean getPartitioningEnabled() { + return HapiProperties.getBooleanProperty(PARTITIONING_ENABLED, false); + } + + public static String getPartitioningCrossPartitionReferenceMode() { + return HapiProperties.getProperty(PARTITIONING_CROSS_PARTITION_REFERENCE_MODE, "NOT_ALLOWED"); + } + + public static Boolean getIncludePartitionInSearchHashes() { + return HapiProperties.getBooleanProperty(PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES, true); + } + public static Boolean getAllowContainsSearches() { return HapiProperties.getBooleanProperty(ALLOW_CONTAINS_SEARCHES, true); } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java index 62fc7be..9cd2e07 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -2,6 +2,9 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.empi.provider.EmpiProviderDstu3; +import ca.uhn.fhir.empi.provider.EmpiProviderR4; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.jpa.api.config.DaoConfig; @@ -10,6 +13,8 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; @@ -37,11 +42,9 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; -import java.util.HashSet; -import java.util.TreeSet; import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.r4.model.Bundle.BundleType; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpHeaders; import org.springframework.web.cors.CorsConfiguration; @@ -49,7 +52,9 @@ import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Set; +import java.util.TreeSet; public class JpaRestfulServer extends RestfulServer { @@ -65,12 +70,12 @@ public class JpaRestfulServer extends RestfulServer { * specified in the properties file. */ ApplicationContext appCtx = (ApplicationContext) getServletContext() - .getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); + .getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); // Customize supported resource types Set supportedResourceTypes = HapiProperties.getSupportedResourceTypes(); if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) { - supportedResourceTypes.add("SearchParameter"); + supportedResourceTypes.add("SearchParameter"); } if (!supportedResourceTypes.isEmpty()) { @@ -115,30 +120,30 @@ public class JpaRestfulServer extends RestfulServer { */ if (fhirVersion == FhirVersionEnum.DSTU2) { IFhirSystemDao systemDao = appCtx - .getBean("mySystemDaoDstu2", IFhirSystemDao.class); + .getBean("mySystemDaoDstu2", IFhirSystemDao.class); JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, - appCtx.getBean(DaoConfig.class)); + appCtx.getBean(DaoConfig.class)); confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.DSTU3) { IFhirSystemDao systemDao = appCtx - .getBean("mySystemDaoDstu3", IFhirSystemDao.class); + .getBean("mySystemDaoDstu3", IFhirSystemDao.class); JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.R4) { IFhirSystemDao systemDao = appCtx - .getBean("mySystemDaoR4", IFhirSystemDao.class); + .getBean("mySystemDaoR4", IFhirSystemDao.class); JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("HAPI FHIR R4 Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.R5) { IFhirSystemDao systemDao = appCtx - .getBean("mySystemDaoR5", IFhirSystemDao.class); + .getBean("mySystemDaoR5", IFhirSystemDao.class); JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("HAPI FHIR R5 Server"); setServerConformanceProvider(confProvider); } else { @@ -219,7 +224,7 @@ public class JpaRestfulServer extends RestfulServer { // manual triggering of a subscription delivery, enable this provider if (false) { // <-- DISABLED RIGHT NOW SubscriptionTriggeringProvider retriggeringProvider = appCtx - .getBean(SubscriptionTriggeringProvider.class); + .getBean(SubscriptionTriggeringProvider.class); registerProvider(retriggeringProvider); } @@ -245,7 +250,7 @@ public class JpaRestfulServer extends RestfulServer { config.addExposedHeader("Location"); config.addExposedHeader("Content-Location"); config.setAllowedMethods( - Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD")); + Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD")); config.setAllowCredentials(HapiProperties.getCorsAllowedCredentials()); // Create the interceptor and register it @@ -256,13 +261,13 @@ public class JpaRestfulServer extends RestfulServer { // If subscriptions are enabled, we want to register the interceptor that // will activate them and match results against them if (HapiProperties.getSubscriptionWebsocketEnabled() || - HapiProperties.getSubscriptionEmailEnabled() || - HapiProperties.getSubscriptionRestHookEnabled()) { + HapiProperties.getSubscriptionEmailEnabled() || + HapiProperties.getSubscriptionRestHookEnabled()) { // Loads subscription interceptors (SubscriptionActivatingInterceptor, SubscriptionMatcherInterceptor) // with activation of scheduled subscription SubscriptionSubmitInterceptorLoader subscriptionInterceptorLoader = appCtx - .getBean(SubscriptionSubmitInterceptorLoader.class); + .getBean(SubscriptionSubmitInterceptorLoader.class); subscriptionInterceptorLoader.start(); // Subscription debug logging @@ -275,43 +280,19 @@ public class JpaRestfulServer extends RestfulServer { IInterceptorBroadcaster interceptorBroadcaster = appCtx.getBean(IInterceptorBroadcaster.class); if (HapiProperties.getAllowCascadingDeletes()) { CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(getFhirContext(), - daoRegistry, interceptorBroadcaster); + daoRegistry, interceptorBroadcaster); getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); } // Binary Storage if (HapiProperties.isBinaryStorageEnabled()) { BinaryStorageInterceptor binaryStorageInterceptor = appCtx - .getBean(BinaryStorageInterceptor.class); + .getBean(BinaryStorageInterceptor.class); getInterceptorService().registerInterceptor(binaryStorageInterceptor); } // Validation - IValidatorModule validatorModule; - // FIXME KHS - /* - switch (fhirVersion) { - case DSTU2: - validatorModule = appCtx.getBean("myInstanceValidatorDstu2", IValidatorModule.class); - break; - case DSTU3: - validatorModule = appCtx.getBean("myInstanceValidatorDstu3", IValidatorModule.class); - break; - case R4: - validatorModule = appCtx.getBean("myInstanceValidatorR4", IValidatorModule.class); - break; - case R5: - validatorModule = appCtx.getBean("myInstanceValidatorR5", IValidatorModule.class); - break; - // These versions are not supported by HAPI FHIR JPA - case DSTU2_HL7ORG: - case DSTU2_1: - default: - validatorModule = null; - break; - } - */ - validatorModule = appCtx.getBean(IValidatorModule.class); + IValidatorModule validatorModule = appCtx.getBean(IValidatorModule.class); if (validatorModule != null) { if (HapiProperties.getValidateRequestsEnabled()) { RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); @@ -343,19 +324,21 @@ public class JpaRestfulServer extends RestfulServer { }); DaoConfig config = appCtx.getBean(DaoConfig.class); config.setBundleTypesAllowedForStorage( - Collections.unmodifiableSet(new TreeSet<>(allowedBundleTypes))); - } - - // Bulk Export - if (HapiProperties.getBulkExportEnabled()) { - registerProvider(appCtx.getBean(BulkDataExportProvider.class)); - } - - - if (HapiProperties.getEmpiEnabled()) { - // FIXME KHS + Collections.unmodifiableSet(new TreeSet<>(allowedBundleTypes))); } + // Bulk Export + if (HapiProperties.getBulkExportEnabled()) { + registerProvider(appCtx.getBean(BulkDataExportProvider.class)); + } + if (HapiProperties.getPartitioningEnabled()) { + PartitionSettings partitionSettings = appCtx.getBean(PartitionSettings.class); + partitionSettings.setPartitioningEnabled(true); + PartitionSettings.CrossPartitionReferenceMode mode = PartitionSettings.CrossPartitionReferenceMode.valueOf(HapiProperties.getPartitioningCrossPartitionReferenceMode()); + partitionSettings.setAllowReferencesAcrossPartitions(mode); + partitionSettings.setIncludePartitionInSearchHashes(HapiProperties.getIncludePartitionInSearchHashes()); + registerProvider(appCtx.getBean(PartitionManagementProvider.class)); + } } } diff --git a/src/main/resources/empi-rules.json b/src/main/resources/empi-rules.json index c305c1c..78e77b6 100644 --- a/src/main/resources/empi-rules.json +++ b/src/main/resources/empi-rules.json @@ -1,30 +1,33 @@ { - "resourceSearchParams" : [ { + "candidateSearchParams" : [ { "resourceType" : "Patient", "searchParam" : "birthdate" }, { - "resourceType" : "All", + "resourceType" : "*", "searchParam" : "identifier" + },{ + "resourceType" : "Patient", + "searchParam" : "general-practitioner" } ], - "filterSearchParams" : [ { - "resourceType" : "All", + "candidateFilterSearchParams" : [ { + "resourceType" : "*", "searchParam" : "active", "fixedValue" : "true" } ], "matchFields" : [ { "name" : "given-name", - "resourceType" : "All", + "resourceType" : "*", "resourcePath" : "name.given", "metric" : "COSINE", "matchThreshold" : 0.8 }, { "name" : "last-name", - "resourceType" : "All", + "resourceType" : "*", "resourcePath" : "name.family", "metric" : "JARO_WINKLER", "matchThreshold" : 0.8 }], - "weightMap" : { + "matchResultMap" : { "given-name" : "POSSIBLE_MATCH", "given-name,last-name" : "MATCH" }, diff --git a/src/main/resources/hapi.properties b/src/main/resources/hapi.properties index 5abe2b1..4c0a4c4 100644 --- a/src/main/resources/hapi.properties +++ b/src/main/resources/hapi.properties @@ -148,7 +148,10 @@ email.password= # Enable Websocket Subscription Channel subscription.websocket.enabled=false -######## # EMPI -######## empi.enabled=false + +# Partitioning +partitioning.enabled=false +partitioning.cross_partition_reference_mode=NOT_ALLOWED +partitioning.partitioning_include_in_search_hashes=true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ffec8d3..9364495 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,6 +9,25 @@ + + DEBUG + ${smile.basedir}/log/empi-troubleshooting.log + + ${smile.basedir}/log/empi-troubleshooting.log.%i.gz + 1 + 9 + + + 5MB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n${log.stackfilter.pattern} + + + + + + diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java index 1eedafd..b5d3bbb 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -8,6 +8,7 @@ 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.test.utilities.JettyUtil; +import ca.uhn.fhir.util.BundleUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.websocket.api.Session; @@ -17,6 +18,8 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Subscription; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -24,11 +27,16 @@ import org.junit.Test; import java.net.URI; import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Optional; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static ca.uhn.fhir.util.TestUtil.waitForSize; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class ExampleServerR4IT { @@ -43,6 +51,7 @@ public class ExampleServerR4IT { HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4"); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); + HapiProperties.setProperty(HapiProperties.EMPI_ENABLED, "true"); ourCtx = FhirContext.forR4(); } @@ -57,9 +66,31 @@ public class ExampleServerR4IT { Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); assertEquals(methodName, pt2.getName().get(0).getFamily()); + + + // Test EMPI + + // Wait until the EMPI message has been processed + await().until(() -> getPeople().size() > 0); + List persons = getPeople(); + + // Verify a Person was created that links to our Patient + Optional personLinkToCreatedPatient = persons.stream() + .map(Person::getLink) + .flatMap(Collection::stream) + .map(Person.PersonLinkComponent::getTarget) + .map(Reference::getReference) + .filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid)) + .findAny(); + assertTrue(personLinkToCreatedPatient.isPresent()); } - @Test + private List getPeople() { + Bundle bundle = ourClient.search().forResource(Person.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute(); + return BundleUtil.toListOfResourcesOfType(ourCtx, bundle, Person.class); + } + + @Test public void testWebsocketSubscription() throws Exception { /* * Create subscription @@ -78,7 +109,7 @@ public class ExampleServerR4IT { IIdType mySubscriptionId = methodOutcome.getId(); // Wait for the subscription to be activated - waitForSize(1, () -> ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size()); + await().until(() -> activeSubscriptionCount() == 3); /* * Attach websocket @@ -117,7 +148,11 @@ public class ExampleServerR4IT { ourClient.delete().resourceById(mySubscriptionId).execute(); } - @AfterClass + private int activeSubscriptionCount() { + return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size(); + } + + @AfterClass public static void afterClass() throws Exception { ourServer.stop(); } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java index 80f7a20..d72b8a9 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java @@ -14,7 +14,13 @@ 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.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Enumerations; +import org.hl7.fhir.r5.model.Observation; +import org.hl7.fhir.r5.model.Patient; +import org.hl7.fhir.r5.model.Subscription; +import org.hl7.fhir.r5.model.SubscriptionTopic; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -29,135 +35,133 @@ import static org.junit.Assert.assertEquals; public class ExampleServerR5IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR5IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR5IT.class); + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static int ourPort; + private static Server ourServer; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr5"); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R5"); - HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); - ourCtx = FhirContext.forR5(); - } + static { + HapiProperties.forceReload(); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr5"); + HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R5"); + HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); + ourCtx = FhirContext.forR5(); + } - @Test - public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); - String methodName = "testCreateResourceConditional"; + @Test + public void testCreateAndRead() { + ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + String methodName = "testCreateResourceConditional"; - Patient pt = new Patient(); - pt.addName().setFamily(methodName); - IIdType id = ourClient.create().resource(pt).execute().getId(); + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + IIdType id = ourClient.create().resource(pt).execute().getId(); - Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); - assertEquals(methodName, pt2.getName().get(0).getFamily()); - } + Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); + assertEquals(methodName, pt2.getName().get(0).getFamily()); + } - @Test - public void testWebsocketSubscription() throws Exception { + @Test + public void testWebsocketSubscription() throws Exception { - /* - * Create topic - */ - Topic topic = new Topic(); - topic.getResourceTrigger().getQueryCriteria().setCurrent("Observation?status=final"); + /* + * Create topic + */ + SubscriptionTopic topic = new SubscriptionTopic(); + topic.getResourceTrigger().getQueryCriteria().setCurrent("Observation?status=final"); - /* - * Create subscription - */ - Subscription subscription = new Subscription(); - subscription.getTopic().setResource(topic); - subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); - subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); + /* + * Create subscription + */ + Subscription subscription = new Subscription(); + subscription.getTopic().setResource(topic); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Enumerations.SubscriptionState.REQUESTED); - Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); - channel.getType().addCoding() - .setSystem("http://terminology.hl7.org/CodeSystem/subscription-channel-type") - .setCode("websocket"); - channel.getPayload().setContentType("application/json"); - subscription.setChannel(channel); + subscription.setContentType("application/json"); + Coding channelType = new Coding().setSystem("http://terminology.hl7.org/CodeSystem/subscription-channel-type") + .setCode("websocket");; + subscription.setChannelType(channelType); - MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); - IIdType mySubscriptionId = methodOutcome.getId(); + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + IIdType mySubscriptionId = methodOutcome.getId(); - // Wait for the subscription to be activated - waitForSize(1, () -> ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size()); + // Wait for the subscription to be activated + waitForSize(1, () -> ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size()); - /* - * Attach websocket - */ + /* + * Attach websocket + */ - WebSocketClient myWebSocketClient = new WebSocketClient(); - SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); + WebSocketClient myWebSocketClient = new WebSocketClient(); + SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); - myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - ourLog.info("Connecting to : {}", echoUri); - Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); - Session session = connection.get(2, TimeUnit.SECONDS); + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); + 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()); + ourLog.info("Connected to WS: {}", session.isOpen()); - /* - * Create a matching resource - */ - Observation obs = new Observation(); - obs.setStatus(Enumerations.ObservationStatus.FINAL); - ourClient.create().resource(obs).execute(); + /* + * Create a matching resource + */ + Observation obs = new Observation(); + obs.setStatus(Enumerations.ObservationStatus.FINAL); + ourClient.create().resource(obs).execute(); - // Give some time for the subscription to deliver - Thread.sleep(2000); + // Give some time for the subscription to deliver + Thread.sleep(2000); - /* - * Ensure that we receive a ping on the websocket - */ - waitForSize(1, () -> mySocketImplementation.myPingCount); + /* + * Ensure that we receive a ping on the websocket + */ + waitForSize(1, () -> mySocketImplementation.myPingCount); - /* - * Clean up - */ - ourClient.delete().resourceById(mySubscriptionId).execute(); - } + /* + * Clean up + */ + ourClient.delete().resourceById(mySubscriptionId).execute(); + } - @AfterClass - public static void afterClass() throws Exception { - ourServer.stop(); - } + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } - @BeforeClass - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); + @BeforeClass + public static void beforeClass() throws Exception { + String path = Paths.get("").toAbsolutePath().toString(); - ourLog.info("Project base path is: {}", path); + ourLog.info("Project base path is: {}", path); - ourServer = new Server(0); + ourServer = new Server(0); - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDisplayName("HAPI FHIR"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); + WebAppContext webAppContext = new WebAppContext(); + webAppContext.setContextPath("/hapi-fhir-jpaserver"); + webAppContext.setDisplayName("HAPI FHIR"); + webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); + webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); + webAppContext.setParentLoaderPriority(true); - ourServer.setHandler(webAppContext); - ourServer.start(); + ourServer.setHandler(webAppContext); + ourServer.start(); - ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourPort = JettyUtil.getPortForStartedServer(ourServer); - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); - } + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } + public static void main(String[] theArgs) throws Exception { + ourPort = 8080; + beforeClass(); + } }