diff --git a/README.md b/README.md index ad0eb2b..f4ca773 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In order to use this sample, you should have: # Running Locally -The easiest way to run this server is to run it directly in Maven using a built-in Jetty server. To do this, execute the following command: +The easiest way to run this server is to run it directly in Maven using a built-in Jetty server. To do this, change `src/main/resources/hapi.properties` `server_address` and `server.base` with the values commented out as *For Jetty, use this* and then execute the following command: ``` mvn jetty:run @@ -62,4 +62,4 @@ To configure the starter app to use MySQL, instead of the default Derby, update * datasource.url=jdbc:mysql://localhost:3306/hapi_dstu3 * hibernate.dialect=org.hibernate.dialect.MySQL5Dialect -It is important to use MySQL5Dialect when using MySQL version 5+. \ No newline at end of file +It is important to use MySQL5Dialect when using MySQL version 5+. diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java b/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java new file mode 100644 index 0000000..ddddbe0 --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.context.FhirVersionEnum; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +public class ApplicationContext extends AnnotationConfigWebApplicationContext { + + public ApplicationContext() { + FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion(); + if (fhirVersion == FhirVersionEnum.DSTU2) { + register(FhirServerConfigDstu2.class, FhirServerConfigCommon.class); + } else if (fhirVersion == FhirVersionEnum.DSTU3) { + register(FhirServerConfigDstu3.class, FhirServerConfigCommon.class); + } else if (fhirVersion == FhirVersionEnum.R4) { + register(FhirServerConfigR4.class, FhirServerConfigCommon.class); + } else { + throw new IllegalStateException(); + } + + if (HapiProperties.getSubscriptionWebsocketEnabled()) { + register(ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig.class); + } + + } + +} 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 7f5266c..517ecc0 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java @@ -90,16 +90,21 @@ public class FhirServerConfigCommon { retVal.setFetchSizeDefaultMaximum(maxFetchSize); ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE? "'unlimited'": maxFetchSize)); - // You can enable these if you want to support Subscriptions from your server - if (this.subscriptionRestHookEnabled) { + // Subscriptions are enabled by channel type + if (HapiProperties.getSubscriptionRestHookEnabled()) { + ourLog.info("Enabling REST-hook subscriptions"); retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); } - - if (this.subscriptionEmailEnabled) { + if (HapiProperties.getSubscriptionEmailEnabled()) { + ourLog.info("Enabling email subscriptions"); retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); } + if (HapiProperties.getSubscriptionWebsocketEnabled()) { + ourLog.info("Enabling websocket subscriptions"); + retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); + } - return retVal; + return retVal; } @Bean diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java index a9957e9..e658930 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java @@ -18,6 +18,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 { @Autowired private DataSource myDataSource; + /** * We override the paging provider definition so that we can customize * the default/max page sizes for search results. You can set these however 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 5d93b5c..ca7f255 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java @@ -38,6 +38,7 @@ public class HapiProperties { static final String SERVER_NAME = "server.name"; static final String SUBSCRIPTION_EMAIL_ENABLED = "subscription.email.enabled"; static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled"; + static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled"; static final String TEST_PORT = "test.port"; static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches"; static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params"; @@ -237,11 +238,11 @@ public class HapiProperties { } public static Boolean getAllowMultipleDelete() { - return HapiProperties.getBooleanProperty(ALLOW_MULTIPLE_DELETE, true); + return HapiProperties.getBooleanProperty(ALLOW_MULTIPLE_DELETE, false); } public static Boolean getAllowExternalReferences() { - return HapiProperties.getBooleanProperty(ALLOW_EXTERNAL_REFERENCES, true); + return HapiProperties.getBooleanProperty(ALLOW_EXTERNAL_REFERENCES, false); } public static Boolean getExpungeEnabled() { @@ -269,11 +270,15 @@ public class HapiProperties { } public static Boolean getSubscriptionEmailEnabled() { - return HapiProperties.getBooleanProperty(SUBSCRIPTION_EMAIL_ENABLED, true); + return HapiProperties.getBooleanProperty(SUBSCRIPTION_EMAIL_ENABLED, false); } public static Boolean getSubscriptionRestHookEnabled() { - return HapiProperties.getBooleanProperty(SUBSCRIPTION_RESTHOOK_ENABLED, true); + return HapiProperties.getBooleanProperty(SUBSCRIPTION_RESTHOOK_ENABLED, false); + } + + public static Boolean getSubscriptionWebsocketEnabled() { + return HapiProperties.getBooleanProperty(SUBSCRIPTION_WEBSOCKET_ENABLED, false); } public static Boolean getAllowContainsSearches() { 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 f4fbece..71bd2d0 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; @@ -14,6 +15,8 @@ import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; @@ -22,7 +25,7 @@ import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.ApplicationContext; import javax.servlet.ServletException; import java.util.List; @@ -30,12 +33,6 @@ import java.util.List; public class JpaRestfulServer extends RestfulServer { private static final long serialVersionUID = 1L; - private AnnotationConfigApplicationContext appCtx; - - @Override - public void destroy() { - appCtx.close(); - } @SuppressWarnings("unchecked") @Override @@ -46,34 +43,33 @@ public class JpaRestfulServer extends RestfulServer { * Create a FhirContext object that uses the version of FHIR * specified in the properties file. */ - FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion(); - setFhirContext(new FhirContext(fhirVersion)); + ApplicationContext appCtx = (ApplicationContext) getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); - appCtx = new AnnotationConfigApplicationContext(); +// if (HapiProperties.getSubscriptionWebsocketEnabled()) { +// appCtx.register(WebsocketDispatcherConfig.class); +// } /* * ResourceProviders are fetched from the Spring context */ + FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion(); List resourceProviders; Object systemProvider; if (fhirVersion == FhirVersionEnum.DSTU2) { - appCtx.register(FhirServerConfigDstu2.class, FhirServerConfigCommon.class); - appCtx.refresh(); resourceProviders = appCtx.getBean("myResourceProvidersDstu2", List.class); systemProvider = appCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); } else if (fhirVersion == FhirVersionEnum.DSTU3) { - appCtx.register(FhirServerConfigDstu3.class, FhirServerConfigCommon.class); - appCtx.refresh(); resourceProviders = appCtx.getBean("myResourceProvidersDstu3", List.class); systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); } else if (fhirVersion == FhirVersionEnum.R4) { - appCtx.register(FhirServerConfigR4.class, FhirServerConfigCommon.class); - appCtx.refresh(); resourceProviders = appCtx.getBean("myResourceProvidersR4", List.class); systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); } else { throw new IllegalStateException(); } + + setFhirContext(appCtx.getBean(FhirContext.class)); + registerProviders(resourceProviders); registerProvider(systemProvider); @@ -173,6 +169,23 @@ public class JpaRestfulServer extends RestfulServer { SubscriptionTriggeringProvider retriggeringProvider = appCtx.getBean(SubscriptionTriggeringProvider.class); registerProvider(retriggeringProvider); } + + // 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()) { + IInterceptorRegistry interceptorRegistry = appCtx.getBean(IInterceptorRegistry.class); + + SubscriptionActivatingInterceptor subscriptionActivatingInterceptor = appCtx.getBean(SubscriptionActivatingInterceptor.class); + interceptorRegistry.registerInterceptor(subscriptionActivatingInterceptor); + + SubscriptionMatcherInterceptor subscriptionMatcherInterceptor = appCtx.getBean(SubscriptionMatcherInterceptor.class); + subscriptionMatcherInterceptor.start(); + interceptorRegistry.registerInterceptor(subscriptionMatcherInterceptor); + } + + } } diff --git a/src/main/resources/hapi.properties b/src/main/resources/hapi.properties index 1a1a5d1..b30faad 100644 --- a/src/main/resources/hapi.properties +++ b/src/main/resources/hapi.properties @@ -8,10 +8,16 @@ fhir_version=DSTU3 # server, put the DNS name of that server here. server_address=http://localhost/fhir/ +# For Jetty, use this: +# server_address=http://localhost:8080/hapi-fhir-jpaserver/fhir/ + # This is the context path for the FHIR endpoint. If this is changed, the # setting above should also be changed. server.base=/fhir +# For Jetty, use this: +# server.base=/hapi-fhir-jpaserver/fhir + default_encoding=JSON etag_support=ENABLED default_page_size=20 @@ -34,14 +40,6 @@ datasource.password= server.name=Local Tester server.id=home test.port= -subscription.email.enabled=true -email.enabled=false -email.from=some@test.com -email.host= -email.port=0 -email.username= -email.password= -subscription.resthook.enabled=true hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory hibernate.format_sql=false @@ -54,4 +52,23 @@ hibernate.cache.use_structured_entries=false hibernate.cache.use_minimal_puts=false hibernate.search.default.directory_provider=filesystem hibernate.search.default.indexBase=target/lucenefiles -hibernate.search.lucene_version=LUCENE_CURRENT \ No newline at end of file +hibernate.search.lucene_version=LUCENE_CURRENT + +################################################## +# Subscriptions +################################################## + +# Enable REST Hook Subscription Channel +subscription.resthook.enabled=false + +# Enable Email Subscription Channel +subscription.email.enabled=false +email.enabled=false +email.from=some@test.com +email.host= +email.port=0 +email.username= +email.password= + +# Enable Websocket Subscription Channel +subscription.websocket.enabled=false diff --git a/src/main/webapp/WEB-INF/templates/tmpl-footer.html b/src/main/webapp/WEB-INF/templates/tmpl-footer.html index bf18c49..1c4f8c7 100644 --- a/src/main/webapp/WEB-INF/templates/tmpl-footer.html +++ b/src/main/webapp/WEB-INF/templates/tmpl-footer.html @@ -1,16 +1,5 @@
-
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 54164b1..b49f111 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,23 +5,20 @@ metadata-complete="false" version="3.1"> - @@ -33,7 +30,9 @@ contextConfigLocation - ca.uhn.fhir.jpa.starter.FhirTesterConfig + + ca.uhn.fhir.jpa.starter.FhirTesterConfig, + 2 diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java index e1aeb5f..d03b519 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java @@ -31,6 +31,7 @@ public class ExampleServerDstu2IT { static { HapiProperties.forceReload(); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU2"); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr2;create=true"); HapiProperties.setProperty(HapiProperties.TEST_PORT, Integer.toString(PortUtil.findFreePort())); ourCtx = FhirContext.forDstu2(); ourPort = HapiProperties.getTestPort(); diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java index 43a5b89..1ee2174 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java @@ -30,6 +30,7 @@ public class ExampleServerDstu3IT { static { HapiProperties.forceReload(); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU3"); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr3;create=true"); HapiProperties.setProperty(HapiProperties.TEST_PORT, Integer.toString(PortUtil.findFreePort())); ourCtx = FhirContext.forDstu3(); ourPort = HapiProperties.getTestPort(); 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 bccdd20..6858b41 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -1,94 +1,170 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; 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.util.PortUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; -import org.hl7.fhir.r4.model.Patient; +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.r4.model.Bundle; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Subscription; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.io.File; import java.io.IOException; +import java.net.URI; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import static ca.uhn.fhir.util.TestUtil.waitForSize; import static org.junit.Assert.assertEquals; public class ExampleServerR4IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class); + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static int ourPort; - private static Server ourServer; - private static String ourServerBase; + private static Server ourServer; + private static String ourServerBase; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); - HapiProperties.setProperty(HapiProperties.TEST_PORT, Integer.toString(PortUtil.findFreePort())); - ourCtx = FhirContext.forR4(); - ourPort = HapiProperties.getTestPort(); - } + static { + HapiProperties.forceReload(); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr4;create=true"); + HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); + HapiProperties.setProperty(HapiProperties.TEST_PORT, Integer.toString(PortUtil.findFreePort())); + HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); + ourCtx = FhirContext.forR4(); + ourPort = HapiProperties.getTestPort(); + } - @Test - public void testCreateAndRead() throws IOException { - ourLog.info("Base URL is: http://localhost:" + ourPort + HapiProperties.getServerBase()); - String methodName = "testCreateResourceConditional"; + @Test + public void testCreateAndRead() throws IOException { + ourLog.info("Base URL is: http://localhost:" + ourPort + HapiProperties.getServerBase()); + 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()); + } - @AfterClass - public static void afterClass() throws Exception { - ourServer.stop(); - } - @BeforeClass - public static void beforeClass() throws Exception { - /* - * This runs under maven, and I'm not sure how else to figure out the target directory from code.. - */ - String path = ExampleServerR4IT.class.getClassLoader().getResource(".keep_hapi-fhir-jpaserver-starter").getPath(); - path = new File(path).getParent(); - path = new File(path).getParent(); - path = new File(path).getParent(); + @Test + public void testWebsocketSubscription() throws Exception { + /* + * Create subscription + */ + 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("Observation?status=final"); - ourLog.info("Project base path is: {}", path); + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); + channel.setPayload("application/json"); + subscription.setChannel(channel); - if (ourPort == 0) { - ourPort = RandomServerPortProvider.findFreePort(); - } - ourServer = new Server(ourPort); + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + IIdType mySubscriptionId = methodOutcome.getId(); - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); + // 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()); - ourServer.setHandler(webAppContext); - ourServer.start(); + /* + * Attach websocket + */ - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - ourServerBase = "http://localhost:" + ourPort + HapiProperties.getServerBase(); - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); - } + WebSocketClient myWebSocketClient = new WebSocketClient(); + SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/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()); + + /* + * Create a matching resource + */ + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + ourClient.create().resource(obs).execute(); + + // Give some time for the subscription to deliver + Thread.sleep(2000); + + /* + * Ensure that we receive a ping on the websocket + */ + waitForSize(1, () -> mySocketImplementation.myPingCount); + + /* + * Clean up + */ + ourClient.delete().resourceById(mySubscriptionId).execute(); + } + + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + /* + * This runs under maven, and I'm not sure how else to figure out the target directory from code.. + */ + String path = ExampleServerR4IT.class.getClassLoader().getResource(".keep_hapi-fhir-jpaserver-starter").getPath(); + path = new File(path).getParent(); + path = new File(path).getParent(); + path = new File(path).getParent(); + + ourLog.info("Project base path is: {}", path); + + if (ourPort == 0) { + ourPort = RandomServerPortProvider.findFreePort(); + } + ourServer = new Server(ourPort); + + WebAppContext webAppContext = new WebAppContext(); + webAppContext.setContextPath("/"); + 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(); + + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + ourServerBase = "http://localhost:" + ourPort + HapiProperties.getServerBase(); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } + + public static void main(String[] theArgs) throws Exception { + ourPort = 8080; + beforeClass(); + } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/SocketImplementation.java b/src/test/java/ca/uhn/fhir/jpa/starter/SocketImplementation.java new file mode 100644 index 0000000..9318a0a --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/SocketImplementation.java @@ -0,0 +1,88 @@ + +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.rest.api.EncodingEnum; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +@WebSocket +public class SocketImplementation { + + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(SocketImplementation.class); + private String myCriteria; + protected String myError; + protected boolean myGotBound; + private List myMessages = new ArrayList(); + protected int myPingCount; + protected String mySubsId; + private Session session; + + public SocketImplementation(String theCriteria, EncodingEnum theEncoding) { + myCriteria = theCriteria; + } + + public List getMessages() { + return myMessages; + } + + public void keepAlive() { + if (this.session != null) { + try { + session.getRemote().sendString("keep alive"); + } catch (Throwable t) { + ourLog.error("Failure", t); + } + } + } + + /** + * This method is executed when the client is connecting to the server. + * In this case, we are sending a message to create the subscription dynamiclly + * + * @param session + */ + @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); + + ourLog.info("Connection: DONE"); + } catch (Throwable t) { + t.printStackTrace(); + ourLog.error("Failure", t); + } + } + + /** + * This is the message handler for the client + * + * @param theMsg + */ + @OnWebSocketMessage + public void onMessage(String theMsg) { + ourLog.info("Got msg: " + theMsg); + myMessages.add(theMsg); + + if (theMsg.startsWith("bound ")) { + myGotBound = true; + mySubsId = (theMsg.substring("bound ".length())); + } else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) { + String text = theMsg.substring(("add " + mySubsId + "\n").length()); + ourLog.info("text: " + text); + } else if (theMsg.startsWith("ping ")) { + myPingCount++; + } else { + myError = "Unexpected message: " + theMsg; + } + } +} \ No newline at end of file