Work in progress bringing SSE to FHIR
This commit is contained in:
parent
d4dda1dace
commit
65dc9e85b7
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue