commit
d69e66f2e9
|
@ -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
|
|
@ -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,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;
|
||||
}
|
||||
}
|
|
@ -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,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"));
|
||||
}
|
||||
}
|
|
@ -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