Merge pull request #581 from jkiddo/master

SSE feature example
This commit is contained in:
James Agnew 2018-03-23 06:25:43 -04:00 committed by GitHub
commit d69e66f2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 466 additions and 0 deletions

View File

@ -0,0 +1,5 @@
This example sets up a FHIR server that can ship out Server-sent events using standard Jersey 2.x components. Start up the server and eg. issue the following curl request 'curl -v -X GET http://localhost:8080/Patient/listen'. The will block curl and once any events are shipped to the server, they will automatically be sent to the curl client.
Changes can be sent to the server on localhost:8080/Patient which accepts any kind of patients that has at least one identifier.
Voila

View File

@ -0,0 +1,91 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>2.3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-jaxrs-sse</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>${jersey_version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.hk2/hk2-locator -->
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-locator</artifactId>
<version>2.4.0-b34</version>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>guice-bridge</artifactId>
<version>2.4.0-b34</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,129 @@
package embedded.example;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.glassfish.jersey.media.sse.EventOutput;
import org.glassfish.jersey.media.sse.OutboundEvent;
import org.glassfish.jersey.media.sse.SseBroadcaster;
import org.glassfish.jersey.media.sse.SseFeature;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Patient;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import jersey.repackaged.com.google.common.collect.Maps;
@Singleton
@Path("Patient")
@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
public class JaxRsPatientProvider extends AbstractJaxRsResourceProvider<Patient> {
private final Map<String, Patient> patients = Maps.newConcurrentMap();
private final SseBroadcaster broadcaster = new SseBroadcaster();
@Inject
public JaxRsPatientProvider() {
super(FhirContext.forDstu3(), JaxRsPatientProvider.class);
}
@Search
public List<Patient> search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
final List<Patient> result = new LinkedList<Patient>();
for (final Patient patient : patients.values()) {
Patient single = null;
if (name == null
|| patient.getName().get(0).getFamilyElement().getValueNotNull().equals(name.getValueNotNull())) {
single = patient;
}
if (single != null) {
result.add(single);
}
}
return result;
}
@Create
public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam final String theConditional)
throws Exception {
storePatient(patient);
final MethodOutcome result = new MethodOutcome().setCreated(true);
result.setResource(patient);
result.setId(new IdType(patient.getId()));
return result;
}
// Conceptual wrapper for storing in a db
private void storePatient(final Patient patient) {
try {
patients.put(patient.getIdentifierFirstRep().getValue(), patient);
// if storing is successful the notify the listeners that listens on
// any patient => patient/*
final String bundleToString = currentPatientsAsJsonString();
broadcaster
.broadcast(new OutboundEvent.Builder().name("patients").data(String.class, bundleToString).build());
} catch (final Exception e) {
e.printStackTrace();
}
}
private String currentPatientsAsJsonString() {
final IParser jsonParser = this.getFhirContext().newJsonParser().setPrettyPrint(true);
final org.hl7.fhir.dstu3.model.Bundle bundle = new org.hl7.fhir.dstu3.model.Bundle();
for (final Patient p : patients.values())
bundle.addEntry(new BundleEntryComponent().setResource(p));
final String bundleToString = jsonParser.encodeResourceToString(bundle);
return bundleToString;
}
@Override
public ETagSupportEnum getETagSupport() {
return ETagSupportEnum.DISABLED;
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@GET
@Path("listen")
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput listenToBroadcast() throws IOException {
final EventOutput eventOutput = new EventOutput();
final String bundleToString = currentPatientsAsJsonString();
eventOutput.write(
new OutboundEvent.Builder().name("patients").data(String.class, bundleToString).build());
this.broadcaster.add(eventOutput);
return eventOutput;
}
}

View File

@ -0,0 +1,132 @@
package embedded.example.jerseyguice;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.Container;
import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
import org.glassfish.jersey.servlet.ServletContainer;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.inject.Injector;
import com.google.inject.Scopes;
import com.google.inject.servlet.ServletModule;
public abstract class GuiceHk2Helper extends ServletModule {
private static final Logger log = LoggerFactory.getLogger(GuiceHk2Helper.class);
@Override
abstract protected void configureServlets();
public interface RestKeyBindingBuilder {
void packages(String... packages);
void packages(Package... packages);
void packages(Class<?>... clazz);
}
protected RestKeyBindingBuilder rest(final String... urlPatterns) {
return new RestKeyBindingBuilderImpl(Arrays.asList(urlPatterns));
}
private class RestKeyBindingBuilderImpl implements RestKeyBindingBuilder {
List<String> paths;
public RestKeyBindingBuilderImpl(final List<String> paths) {
this.paths = paths;
}
private boolean checkIfPackageExistsAndLog(final String packge) {
boolean exists = false;
final String resourcePath = packge.replace(".", "/");
final URL resource = getClass().getClassLoader().getResource(resourcePath);
if (resource != null) {
exists = true;
log.info("rest(" + paths + ").packages(" + packge + ")");
} else {
log.info("No Beans in '" + packge + "' found. Requests " + paths + " will fail.");
}
return exists;
}
@Override
public void packages(final String... packages) {
final StringBuilder sb = new StringBuilder();
for (final String pkg : packages) {
if (sb.length() > 0) {
sb.append(',');
}
checkIfPackageExistsAndLog(pkg);
sb.append(pkg);
}
final Map<String, String> params = new HashMap<>();
params.put("javax.ws.rs.Application", GuiceResourceConfig.class.getCanonicalName());
if (sb.length() > 0) {
params.put("jersey.config.server.provider.packages", sb.toString());
}
bind(ServletContainer.class).in(Scopes.SINGLETON);
for (final String path : paths) {
serve(path).with(ServletContainer.class, params);
}
}
@Override
public void packages(final Package... packages) {
packages(FluentIterable.from(packages).transform(new Function<Package, String>() {
@Override
public String apply(final Package arg0) {
return arg0.getName();
}
}).toArray(String.class));
}
@Override
public void packages(final Class<?>... clazz) {
packages(FluentIterable.from(clazz).transform(new Function<Class<?>, String>() {
@Override
public String apply(final Class<?> arg0) {
return arg0.getPackage().getName();
}
}).toArray(String.class));
}
}
}
class GuiceResourceConfig extends ResourceConfig {
public GuiceResourceConfig() {
register(new ContainerLifecycleListener() {
@Override
public void onStartup(final Container container) {
final ServletContainer servletContainer = (ServletContainer) container;
final ServiceLocator serviceLocator = container.getApplicationHandler().getServiceLocator();
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
final GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
final Injector injector = (Injector) servletContainer.getServletContext()
.getAttribute(Injector.class.getName());
guiceBridge.bridgeGuiceInjector(injector);
}
@Override
public void onReload(final Container container) {
}
@Override
public void onShutdown(final Container container) {
}
});
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2016 Aberger Software GmbH. All Rights Reserved.
* http://www.aberger.at
*
* 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.
*/
package embedded.example.jerseyguice;
import java.awt.Desktop;
import java.net.URI;
import java.util.EnumSet;
import java.util.List;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.slf4j.bridge.SLF4JBridgeHandler;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import embedded.example.JaxRsPatientProvider;
public class GuiceJersey2ServletContextListener extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
final List<Module> modules = Lists.newArrayList();
modules.add(new GuiceHk2Helper() {
@Override
protected void configureServlets() {
// bind(JaxRsPatientProvider.class).in(Scopes.SINGLETON);
rest("/*").packages(JaxRsPatientProvider.class);
}
});
return Guice.createInjector(Stage.PRODUCTION, modules);
}
public static void main(final String[] args) throws Exception {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
final Server server = new Server(8080);
final ServletContextHandler sch = new ServletContextHandler(server, "/");
sch.addEventListener(new GuiceJersey2ServletContextListener());
sch.addFilter(GuiceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
server.start();
Desktop.getDesktop().browse(new URI("http://localhost:8080/Patient"));
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<web-app>
<filter>
<filter-name>Guice Filter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Guice Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>embedded.example.ContextListener</listener-class>
</listener>
<!-- <context-param> <param-name>username</param-name> <param-value>username</param-value>
</context-param> <context-param> <param-name>password</param-name> <param-value>password</param-value>
</context-param> -->
<context-param>
<param-name>serverAddress</param-name>
<param-value>http://fhirtest.uhn.ca/baseDstu2</param-value>
</context-param>
</web-app>

View File

@ -0,0 +1,14 @@
package test;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class WarTester {
public static void main(String[] args) throws Exception {
final Server server = new Server(8080);
server.setHandler(new WebAppContext("target/fhirtester.war", "/"));
server.start();
}
}