Work in progress bringing SSE to FHIR

This commit is contained in:
jkv 2017-02-14 08:01:44 +01:00
parent d4dda1dace
commit 65dc9e85b7
6 changed files with 478 additions and 0 deletions

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,122 @@
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.IdType;
import org.hl7.fhir.dstu3.model.Identifier;
import org.hl7.fhir.dstu3.model.Patient;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
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);
new Thread() {
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
final int id = patients.size() + 1;
final Patient p = new Patient();
final Identifier i = new Identifier();
i.setValue(id + "");
p.addName().setFamily("John " + i);
p.getIdentifier().add(i);
p.setId(new IdType(id));
patients.put(id + "", p);
broadcaster.broadcast(new OutboundEvent.Builder().name("patients").data(String.class, patients.size() + "").build());
}
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
@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 {
patients.put(patient.getIdentifierFirstRep().getId(), patient);
final MethodOutcome result = new MethodOutcome().setCreated(true);
result.setResource(patient);
result.setId(new IdType(patient.getId()));
return result;
}
@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();
eventOutput.write(
new OutboundEvent.Builder().name("patient count").data(String.class, patients.size() + "").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,97 @@
/*
* 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 java.util.concurrent.ConcurrentHashMap;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Patient;
import org.slf4j.bridge.SLF4JBridgeHandler;
import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.Stage;
import com.google.inject.name.Names;
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 AbstractModule() {
@Override
protected void configure() {
final ConcurrentHashMap<String, List<Patient>> patients = new ConcurrentHashMap<String, List<Patient>>();
for (int i = 0; i < 20; i++) {
final Patient patient = new Patient();
patient.getName().add(new HumanName().setFamily("Random Patient " + i));
patient.setId(new IdType("Patient", "" + i, "" + 1));
patients.put(String.valueOf(i), Lists.newArrayList(patient));
}
}
});
modules.add(new GuiceHk2Helper() {
@Override
protected void configureServlets() {
bind(String.class).annotatedWith(Names.named("1")).toInstance("sad");
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();
}
}