Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2015-10-23 08:22:40 -04:00
commit 69658ab754
18 changed files with 484 additions and 9 deletions

View File

@ -0,0 +1,47 @@
<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>1.2</version>
</parent>
<packaging>jar</packaging>
<artifactId>hapi-fhir-base-example-embedded-ws</artifactId>
<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>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
package embedded;
import java.util.Map;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.guice.JerseyServletModule;
import filters.CharsetResponseFilter;
import filters.CorsResponseFilter;
public class ContextListener extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() {
@Override
protected void configureServlets() {
final Map<String, String> params = ImmutableMap
.<String, String> builder()
.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
Joiner.on(";").join(
CharsetResponseFilter.class.getName(),
CorsResponseFilter.class.getName(),
GZIPContentEncodingFilter.class
.getName())).build();
serve("/model/*").with(FhirRestfulServlet.class, params);
}
});
}
}

View File

@ -0,0 +1,59 @@
package embedded;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Singleton;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
@Singleton
public class FhirRestfulServlet extends RestfulServer {
/**
*
*/
private static final long serialVersionUID = -3931111342737918913L;
public FhirRestfulServlet() {
super(FhirContext.forDstu2()); // Support DSTU2
}
/**
* This method is called automatically when the servlet is initializing.
*/
@Override
public void initialize() {
/*
* Two resource providers are defined. Each one handles a specific type
* of resource.
*/
final List<IResourceProvider> providers = new ArrayList<IResourceProvider>();
providers.add(new SomeResourceProvider());
setResourceProviders(providers);
/*
* Use a narrative generator. This is a completely optional step, but
* can be useful as it causes HAPI to generate narratives for resources
* which don't otherwise have one.
*/
final INarrativeGenerator narrativeGen = new DefaultThymeleafNarrativeGenerator();
getFhirContext().setNarrativeGenerator(narrativeGen);
/*
* Tells HAPI to use content types which are not technically FHIR
* compliant when a browser is detected as the requesting client. This
* prevents browsers from trying to download resource responses instead
* of displaying them inline which can be handy for troubleshooting.
*/
setUseBrowserFriendlyContentTypes(true);
registerInterceptor(new ResponseHighlighterInterceptor());
}
}

View File

@ -0,0 +1,25 @@
package embedded;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import com.google.inject.servlet.GuiceFilter;
public class ServerStartup {
public static void main(final String[] args) throws Exception {
final Server server = new Server(9090);
final ServletContextHandler sch = new ServletContextHandler(server, "/");
sch.addEventListener(new ContextListener());
sch.addFilter(GuiceFilter.class, "/*",
EnumSet.of(DispatcherType.REQUEST));
sch.addServlet(DefaultServlet.class, "/");
server.start();
}
}

View File

@ -0,0 +1,28 @@
package embedded;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.dstu2.resource.Practitioner;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SomeResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Practitioner.class;
}
@Search()
public List<Practitioner> findPractitionersByName(
@RequiredParam(name = Practitioner.SP_NAME) final StringDt theName) {
throw new UnprocessableEntityException(
"Please provide more than 4 characters for the name");
}
}

View File

@ -0,0 +1,24 @@
package filters;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class CharsetResponseFilter implements ContainerResponseFilter {
@Override
public ContainerResponse filter(final ContainerRequest request,
final ContainerResponse response) {
final MediaType contentType = response.getMediaType();
if (contentType != null) {
response.getHttpHeaders().putSingle(HttpHeaders.CONTENT_TYPE,
contentType.toString() + ";charset=UTF-8");
}
return response;
}
}

View File

@ -0,0 +1,33 @@
package filters;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class CorsResponseFilter implements ContainerResponseFilter {
@Override
public ContainerResponse filter(final ContainerRequest req,
final ContainerResponse contResp) {
final ResponseBuilder resp = Response.fromResponse(contResp
.getResponse());
resp.header("Access-Control-Allow-Origin", "*").header(
"Access-Control-Allow-Methods", "GET, POST, OPTIONS");
final String reqHead = req
.getHeaderValue("Access-Control-Request-Headers");
if (null != reqHead && !reqHead.equals("")) {
resp.header("Access-Control-Allow-Headers", reqHead);
}
contResp.setResponse(resp.build());
return contResp;
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.method; package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -138,6 +140,9 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
return false; return false;
} }
} }
if (isNotBlank(theRequest.getCompartmentName())) {
return false;
}
if (theRequest.getRequestType() != RequestTypeEnum.GET) { if (theRequest.getRequestType() != RequestTypeEnum.GET) {
ourLog.trace("Method {} doesn't match because request type is not GET: {}", theRequest.getId(), theRequest.getRequestType()); ourLog.trace("Method {} doesn't match because request type is not GET: {}", theRequest.getId(), theRequest.getRequestType());
return false; return false;
@ -198,6 +203,9 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
return resource; return resource;
case BUNDLE_PROVIDER: case BUNDLE_PROVIDER:
return new SimpleBundleProvider(resource); return new SimpleBundleProvider(resource);
case BUNDLE_RESOURCE:
case METHOD_OUTCOME:
break;
} }
throw new IllegalStateException("" + getMethodReturnType()); // should not happen throw new IllegalStateException("" + getMethodReturnType()); // should not happen

View File

@ -43,6 +43,9 @@ import org.hibernate.search.annotations.Field;
}) })
public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
/*
* Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
*/
public static final int MAX_LENGTH = 200; public static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -43,7 +43,10 @@ import org.hibernate.search.annotations.Indexed;
//@formatter:on //@formatter:on
public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam {
public static final int MAX_LENGTH = 256; /*
* Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
*/
public static final int MAX_LENGTH = 255;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -167,23 +167,23 @@
</resource> </resource>
<resource> <resource>
<directory>../hapi-fhir-structures-dstu/src/main/resources</directory> <directory>../hapi-fhir-structures-dstu/src/main/resources</directory>
<filtering>true</filtering> <filtering>false</filtering>
</resource> </resource>
<resource> <resource>
<directory>../hapi-fhir-structures-dstu/target/generated-resources/tinder</directory> <directory>../hapi-fhir-structures-dstu/target/generated-resources/tinder</directory>
<filtering>true</filtering> <filtering>false</filtering>
</resource> </resource>
<resource> <resource>
<directory>../hapi-fhir-structures-dstu2/src/main/resources</directory> <directory>../hapi-fhir-structures-dstu2/src/main/resources</directory>
<filtering>true</filtering> <filtering>false</filtering>
</resource> </resource>
<resource> <resource>
<directory>../hapi-fhir-structures-dstu2/target/generated-resources/tinder</directory> <directory>../hapi-fhir-structures-dstu2/target/generated-resources/tinder</directory>
<filtering>true</filtering> <filtering>false</filtering>
</resource> </resource>
<resource> <resource>
<directory>../hapi-fhir-structures-hl7org-dstu2/src/resources/java</directory> <directory>../hapi-fhir-structures-hl7org-dstu2/src/resources/java</directory>
<filtering>true</filtering> <filtering>false</filtering>
</resource> </resource>
</resources> </resources>
</build> </build>

View File

@ -0,0 +1,187 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
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.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class CompartmentDstu2Test {
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompartmentDstu2Test.class);
private static int ourPort;
private static Server ourServer;
private static String ourLastMethod;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static IdDt ourLastId;
@Before
public void before() {
ourLastMethod = null;
ourLastId = null;
}
@Test
public void testReadFirst() throws Exception {
init(new TempPatientResourceProvider());
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals("read", ourLastMethod);
assertEquals("Patient", ourLastId.getResourceType());
assertEquals("123", ourLastId.getIdPart());
assertThat(responseContent, startsWith("<Patient"));
}
@Test
public void testCompartmentSecond() throws Exception {
init(new TempPatientResourceProvider());
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/Encounter");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals("searchEncounterCompartment", ourLastMethod);
assertEquals("Patient", ourLastId.getResourceType());
assertEquals("123", ourLastId.getIdPart());
assertThat(responseContent, startsWith("<Bundle"));
assertThat(responseContent, containsString("<Encounter"));
}
@Test
public void testCompartmentSecond2() throws Exception {
init(new TempPatientResourceProvider());
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/Observation");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals("searchObservationCompartment", ourLastMethod);
assertEquals("Patient", ourLastId.getResourceType());
assertEquals("123", ourLastId.getIdPart());
assertThat(responseContent, startsWith("<Bundle"));
assertThat(responseContent, containsString("<Observation"));
}
@After
public void after() throws Exception {
ourServer.stop();
ourClient.close();
}
public void init(IResourceProvider... theProviders) throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(theProviders);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class TempPatientResourceProvider implements IResourceProvider {
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Read()
public Patient method1Read(final @IdParam IdDt theId) {
ourLastMethod = "read";
ourLastId = theId;
Patient patient = new Patient();
patient.setId(theId);
return patient;
}
@Search(compartmentName = "Encounter")
public List<Encounter> method2SearchCompartment(final @IdParam IdDt theId) {
ourLastId = theId;
ourLastMethod = "searchEncounterCompartment";
System.out.println("Encounter compartment search");
List<Encounter> encounters = new ArrayList<Encounter>();
Encounter encounter = new Encounter();
encounter.setId("1");
encounter.setPatient(new ResourceReferenceDt(theId));
encounters.add(encounter);
return encounters;
}
@Search(compartmentName = "Observation")
public List<Observation> method2SearchCompartment2(final @IdParam IdDt theId) {
ourLastId = theId;
ourLastMethod = "searchObservationCompartment";
System.out.println("Encounter compartment search");
List<Observation> encounters = new ArrayList<Observation>();
Observation obs = new Observation();
obs.setId("1");
obs.setSubject(new ResourceReferenceDt(theId));
encounters.add(obs);
return encounters;
}
}
}

View File

@ -27,6 +27,7 @@ public class TesterConfig {
public IServerBuilderStep1 addServer() { public IServerBuilderStep1 addServer() {
ServerBuilder retVal = new ServerBuilder(); ServerBuilder retVal = new ServerBuilder();
myServerBuilders.add(retVal);
return retVal; return retVal;
} }

View File

@ -190,6 +190,10 @@
<id>botunge</id> <id>botunge</id>
<name>Thomas Andersen</name> <name>Thomas Andersen</name>
</developer> </developer>
<developer>
<id>samlanfranchi</id>
<name>Sam Lanfranchi</name>
</developer>
</developers> </developers>
<licenses> <licenses>
@ -1374,4 +1378,7 @@
</profile> </profile>
</profiles> </profiles>
<modules>
<module>hapi-fhir-base-example-embedded-ws</module>
</modules>
</project> </project>

View File

@ -194,6 +194,19 @@
handle JSON responses or use interceptors. Thanks to handle JSON responses or use interceptors. Thanks to
JT for reporting! JT for reporting!
</action> </action>
<action type="add">
JPA server maximumn length for a URI search parameter has been reduced from
256 to 255 in order to accomodate MySQL's indexing requirements
</action>
<action type="fix" issue="242">
Server failed to respond correctly to compartment search operations
if the same provider also contained a read operation. Thanks to GitHub user
@am202 for reporting!
</action>
<action type="fix" issue="245">
FIx issue in testpage-overlay's new Java configuration where only the first
configured server actually gets used.
</action>
</release> </release>
<release version="1.2" date="2015-09-18"> <release version="1.2" date="2015-09-18">
<action type="add"> <action type="add">

2
vagrant/Vagrantfile vendored
View File

@ -86,7 +86,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
proxy_port: 80, proxy_port: 80,
# ssl_port: 443, # ssl_port: 443,
authbind: 'yes', authbind: 'yes',
java_options: '-Dfhir.logdir=/var/log/fhir -Dfhir.db.location=/var/fhirdb' java_options: '-Dfhir.logdir=/var/log/fhir -Dfhir.db.location=/var/fhirdb -Dfhir.db.location.dstu2=/var/fhirdb2 -Dfhir.lucene.location.dstu2=/var/fhirlucene2'
}, },
mysql: { mysql: {
version: '5.6', version: '5.6',