Fixing bug 3- Instance history calls incorrectly get routed to vread

method
This commit is contained in:
James Agnew 2014-07-21 14:15:22 -04:00
parent 4713639713
commit 64481185c7
8 changed files with 252 additions and 13 deletions

View File

@ -27,6 +27,14 @@
Add interceptor framework for clients (annotation based and generic), and add interceptors
for configurable logging, capturing requests and responses, and HTTP basic auth.
</action>
<action type="fix">
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
</action>
<action type="fix" issue="3">
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
defined. Thanks to Neal Acharya from UHN for surfacing this one!
</action>
</release>
<release version="0.4" date="2014-Jul-13">
<action type="add">

View File

@ -43,6 +43,7 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -106,6 +107,9 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
if (mySupportsVersion == false && myVersionIdIndex == null) {
return false;
}
if (theRequest.getVersionId() == null) {
return false;
}
} else if (!StringUtils.isBlank(theRequest.getOperation())) {
return false;
}
@ -189,6 +193,8 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
return Collections.singletonList(resource);
case RESOURCE:
return resource;
case BUNDLE_PROVIDER:
return new SimpleBundleProvider(resource);
}
throw new IllegalStateException("" + getMethodReturnType()); // should not happen

View File

@ -59,6 +59,7 @@
<item name="RESTful Operations" href="./doc_rest_operations.html" />
<item name="Extensions" href="./doc_extensions.html" />
<item name="Narrative Generator" href="./doc_narrative.html" />
<item name="Logging" href="./doc_logging.html" />
<item name="Tinder Plugin" href="./doc_tinder.html" />
</menu>

View File

@ -2,7 +2,7 @@
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<properties>
<title>Introduction - HAPI FHIR</title>
<title>HAPI FHIR - Free/Open Source Implementation of HL7 FHIR for Java</title>
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
</properties>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<properties>
<title>Logging - HAPI FHIR</title>
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
</properties>
<body>
<section name="Logging">
<macro name="toc">
</macro>
<p>
Java has an abundance of logging frameworks, none of which are perfect. Many libraries
depend on one or more of these frameworks but also have dependencies who depend on a
different one. These dependencies can cause conflicts and be very irritating to solve.
</p>
<p>
Unfortunately HAPI is not immune to this issue.
</p>
<subsection name="SLF4j">
<p>
HAPI uses
<a href="http://www.slf4j.org/">SLF4j</a>
for all internal logging. SLF4j is a &quot;logging facade&quot; framework, meaning
that it doesn't actually handle log output (i.e. it isn't actually writing log lines
to disk) but rather it is able to delegate that task to any of a number of
underlying frameworks (e.g. log4j, logback, JDK logging, etc.)
</p>
<p>
This means that in order to successfully log anything, you will need to
add two (or three) dependency JARs to your application:
</p>
<ul>
<li><b>slf4j-api-vXX.jar</b>: This is the SLF4j API and is neccesary for HAPI to function</li>
<li>
An actual logging implementation, as well as its SLF4j binding. For example:
<ul>
<li>
The recommended logging framework to use is Logback. Logback is absolutely
not neccesary for HAPI to function correctly, but it has a number of nice features
and is a good default choice. To use logback, you would include
<code><b>logback-vXX.jar</b></code>.
</li>
<li>
If you wanted to use log4j you would include <code><b>log4j-vXX.jar</b></code>
as well as <code><b>slf4j-log4j-vXX.jar</b></code>. Log4j is a mature
framework that is very widely used.
</li>
<li>
If you wanted to use JDK logging (aka java.util.Logging) you would include
<code><b>slf4j-jdk14-vXX.jar</b></code>. JDK logging is included with
Java but is not particularly full featured compared to many other frameworks.
</li>
</ul>
</li>
</ul>
</subsection>
<subsection name="Commons-Logging">
<p>
Note that HAPI's client uses Apache HttpComponents Client internally, and that
library uses Apache Commons Logging as a logging facade. The recommended approach to
using HAPI is to not include any commons-logging JAR in your application, but rather to
include a copy of jcl-over-slf4j-vXX.jar. This JAR will simulate commons-logging,
but will redirect its logging statements to the same target as SLF4j has been
configured to.
</p>
</subsection>
</section>
<section name="Client Payload Logging">
<p>
To enable detailed logging of client requests and responses (what URL is being requested, what headers and payload
are being received, etc.), an interceptor may be added to the client which logs each transaction. See
<a href="./doc_rest_client.html#req_resp_logging">Logging Requests and Responses</a> for more information.
</p>
</section>
</body>
</document>

View File

@ -256,6 +256,7 @@
</subsection>
<a name="req_resp_logging"/>
<subsection name="Logging Requests and Responses">
<p>

View File

@ -20,9 +20,14 @@ import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
@ -32,21 +37,80 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class HistoryTest {
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HistoryTest.class);
private static int ourPort;
private static Server ourServer;
/**
* We test this here because of bug 3- At one point VRead would "steal" instance history calls and handle them
*/
@Test
public void testHistory() throws Exception {
public void testVread() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history/456");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Patient bundle = new FhirContext().newXmlParser().parseResource(Patient.class, responseContent);
assertEquals("vread", bundle.getNameFirstRep().getFamilyFirstRep().getValue());
}
}
@Test
public void testServerHistory() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(2, new FhirContext().newXmlParser().parseBundle(responseContent).getEntries().size());
Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("http://localhost:" + ourPort +"/Patient/h1/_history/1", bundle.getEntries().get(0).getLinkSelf().getValue());
assertEquals("http://localhost:" + ourPort +"/Patient/h1/_history/2", bundle.getEntries().get(1).getLinkSelf().getValue());
}
}
@Test
public void testInstanceHistory() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("http://localhost:" + ourPort +"/Patient/ih1/_history/1", bundle.getEntries().get(0).getLinkSelf().getValue());
assertEquals("http://localhost:" + ourPort +"/Patient/ih1/_history/2", bundle.getEntries().get(1).getLinkSelf().getValue());
}
}
@Test
public void testTypeHistory() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("http://localhost:" + ourPort +"/Patient/th1/_history/1", bundle.getEntries().get(0).getLinkSelf().getValue());
assertEquals("http://localhost:" + ourPort +"/Patient/th1/_history/2", bundle.getEntries().get(1).getLinkSelf().getValue());
}
}
@AfterClass
public static void afterClass() throws Exception {
@ -58,11 +122,13 @@ public class HistoryTest {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
DummyProvider patientProvider = new DummyProvider();
DummyPlainProvider plainProvider = new DummyPlainProvider();
DummyResourceProvider patientProvider = new DummyResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setPlainProviders(patientProvider);
servlet.setPlainProviders(plainProvider);
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
@ -75,27 +141,84 @@ public class HistoryTest {
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider {
public static class DummyResourceProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Read(version=true)
public Patient vread(@IdParam IdDt theId) {
Patient retVal = new Patient();
retVal.addName().addFamily("vread");
retVal.setId(theId);
return retVal;
}
@History
public List<Patient> findPatient(@Since InstantDt theSince) {
public List<Patient> instanceHistory(@IdParam IdDt theId) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
patient.setId("Patient/ih1/_history/1");
patient.addName().addFamily("history");
retVal.add(patient);
Patient patient2 = new Patient();
patient2.setId("Patient/1/_history/2");
patient2.setId("Patient/ih1/_history/2");
patient2.addName().addFamily("history");
retVal.add(patient2);
return retVal;
}
@History
public List<Patient> typeHistory() {
ArrayList<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
patient.setId("Patient/th1/_history/1");
patient.addName().addFamily("history");
retVal.add(patient);
Patient patient2 = new Patient();
patient2.setId("Patient/th1/_history/2");
patient2.addName().addFamily("history");
retVal.add(patient2);
return retVal;
}
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPlainProvider {
@History
public List<Patient> history(@Since InstantDt theSince) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
patient.setId("Patient/h1/_history/1");
patient.addName().addFamily("history");
retVal.add(patient);
Patient patient2 = new Patient();
patient2.setId("Patient/h1/_history/2");
patient2.addName().addFamily("history");
retVal.add(patient2);
return retVal;
}
}
}

View File

@ -42,8 +42,11 @@ public class ReadTest {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class,responseContent).getIdentifierFirstRep();
assertEquals("1", dt.getSystem().getValueAsString());
assertEquals(null, dt.getValue().getValueAsString());
}
@ -56,6 +59,8 @@ public class ReadTest {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/2");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class,responseContent).getIdentifierFirstRep();
assertEquals("1", dt.getSystem().getValueAsString());