Fixing bug 3- Instance history calls incorrectly get routed to vread
method
This commit is contained in:
parent
4713639713
commit
64481185c7
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 "logging facade" 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>
|
|
@ -256,6 +256,7 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<a name="req_resp_logging"/>
|
||||
<subsection name="Logging Requests and Responses">
|
||||
|
||||
<p>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue