Accept conditional updates with _id as parameter

This commit is contained in:
James Agnew 2016-05-16 19:11:25 -04:00
parent 309b67c010
commit 7942d69d5d
6 changed files with 198 additions and 49 deletions

View File

@ -83,16 +83,6 @@ class ConditionalParamBinder implements IParameter {
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) { if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
return null; return null;
} }
boolean haveParam = false;
for (String next : theRequest.getParameters().keySet()) {
if (!next.startsWith("_")) {
haveParam=true;
break;
}
}
if (!haveParam) {
return null;
}
int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?'); int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?');
return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex); return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex);

View File

@ -528,6 +528,40 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
} }
@Test
public void testTransactionCreateInlineMatchUrlWithOneMatchLastUpdated() {
Bundle request = new Bundle();
Observation o = new Observation();
o.getCode().setText("Some Observation");
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Observation?_lastUpdated=gt2011-01-01");
Bundle resp = mySystemDao.transaction(mySrd, request);
assertEquals(1, resp.getEntry().size());
BundleEntryComponent respEntry = resp.getEntry().get(0);
assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus());
assertThat(respEntry.getResponse().getLocation(), containsString("Observation/"));
assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1"));
assertEquals("1", respEntry.getResponse().getEtag());
/*
* Second time should not update
*/
request = new Bundle();
o = new Observation();
o.getCode().setText("Some Observation");
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Observation?_lastUpdated=gt2011-01-01");
resp = mySystemDao.transaction(mySrd, request);
assertEquals(1, resp.getEntry().size());
respEntry = resp.getEntry().get(0);
assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus());
assertThat(respEntry.getResponse().getLocation(), containsString("Observation/"));
assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1"));
assertEquals("1", respEntry.getResponse().getEtag());
}
@Test @Test
public void testTransactionCreateInlineMatchUrlWithNoMatches() { public void testTransactionCreateInlineMatchUrlWithNoMatches() {
String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches"; String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches";

View File

@ -0,0 +1,57 @@
<Bundle xmlns="http://hl7.org/fhir">
<type value="transaction"/>
<id value="f9935843-19a2-4a1a-4016-7bea52de77f8"/>
<entry>
<fullUrl value="urn:uuid:47709cc7-b3ec-4abc-9d26-3df3d3d57907"/>
<resource>
<Practitioner xmlns="http://hl7.org/fhir">
<identifier>
<use value="official"/>
<!--Reuse value from CDA -->
<system value="urn:oid:2.16.840.1.113883.2.4.6.3"/>
<!--Reuse OIDs from CDA -->
<!-- BSN identification system -->
<value value="567IUI51C157"/>
</identifier>
<name>
<use value="official"/>
<family value="Heps"/>
<given value="Simone"/>
<suffix value="MD"/>
</name>
<telecom>
<system value="phone"/>
<value value="020556936"/>
<use value="work"/>
</telecom>
<telecom>
<system value="email"/>
<value value="S.M.Heps@bmc.nl"/>
<use value="work"/>
</telecom>
<telecom>
<system value="fax"/>
<value value="0205669283"/>
<use value="work"/>
</telecom>
<address>
<use value="work"/>
<line value="Galapagosweg 91"/>
<city value="Den Burg"/>
<postalCode value="9105 PZ"/>
<country value="NLD"/>
<!-- ISO 3166 Codes (Countries) -->
</address>
<gender value="female"/>
<birthDate value="1971-11-07"/>
</Practitioner>
</resource>
<request>
<!--For this entry we only want to create the practitioner based on the entry data if we dont find one -->
<method value="POST"/>
<url value="Practitioner"/>
<!--Use DrProviderNumber Paul to add more syntax below -->
<ifNoneExist value="Practitioner?identifier=567IUI51C157"/>
</request>
</entry>
</Bundle>

View File

@ -31,6 +31,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
@ -49,6 +50,26 @@ public class CreateDstu3Test {
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
/**
* #342
*/
@Test
public void testCreateWithInvalidContent() throws Exception {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity("FOO", ContentType.parse("application/xml+fhir; charset=utf-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
String expected = "<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\"Failed to parse request body as XML resource. Error was: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'F' (code 70) in prolog; expected '&lt;'&#xa; at [row,col {unknown-source}]: [1,1]\"/></issue></OperationOutcome>";
assertEquals(expected, responseContent);
}
@Test @Test
public void testRead() throws Exception { public void testRead() throws Exception {
@ -76,26 +97,6 @@ public class CreateDstu3Test {
//@formatter:on //@formatter:on
} }
/**
* #342
*/
@Test
public void testCreateWithInvalidContent() throws Exception {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity("FOO", ContentType.parse("application/xml+fhir; charset=utf-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
String expected = "<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\"Failed to parse request body as XML resource. Error was: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'F' (code 70) in prolog; expected '&lt;'&#xa; at [row,col {unknown-source}]: [1,1]\"/></issue></OperationOutcome>";
assertEquals(expected, responseContent);
}
@Test @Test
public void testSearch() throws Exception { public void testSearch() throws Exception {
@ -156,6 +157,11 @@ public class CreateDstu3Test {
public static class PatientProvider implements IResourceProvider { public static class PatientProvider implements IResourceProvider {
@Create()
public MethodOutcome create(@ResourceParam Patient theIdParam) {
return new MethodOutcome(new IdType("Patient", "1"), true);
}
@Override @Override
public Class<Patient> getResourceType() { public Class<Patient> getResourceType() {
return Patient.class; return Patient.class;
@ -169,11 +175,6 @@ public class CreateDstu3Test {
return p0; return p0;
} }
@Create()
public MethodOutcome read(@ResourceParam Patient theIdParam) {
return new MethodOutcome(new IdType("Patient", "1"), true);
}
@Search @Search
public List<IBaseResource> search() { public List<IBaseResource> search() {
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>(); ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();

View File

@ -6,6 +6,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
@ -19,11 +20,13 @@ import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
@ -63,6 +66,53 @@ public class UpdateDstu3Test {
} }
@Test
public void testUpdateConditional() throws Exception {
Patient patient = new Patient();
patient.setId("001");
patient.addIdentifier().setValue("002");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?_id=001");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("Patient?_id=001",ourConditionalUrl);
assertEquals(null, ourId);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testUpdateNormal() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertNull(ourConditionalUrl);
assertEquals("Patient/001", ourId.getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test @Test
public void testUpdateWrongUrlInBody() throws Exception { public void testUpdateWrongUrlInBody() throws Exception {
@ -81,7 +131,9 @@ public class UpdateDstu3Test {
ourLog.info("Response was:\n{}", responseContent); ourLog.info("Response was:\n{}", responseContent);
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent); OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
assertEquals("Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"3\" does not match URL ID of \"001\"", oo.getIssueFirstRep().getDiagnostics()); assertEquals(
"Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"3\" does not match URL ID of \"001\"",
oo.getIssueFirstRep().getDiagnostics());
assertEquals(400, status.getStatusLine().getStatusCode()); assertEquals(400, status.getStatusLine().getStatusCode());
assertNull(status.getFirstHeader("location")); assertNull(status.getFirstHeader("location"));
@ -115,7 +167,15 @@ public class UpdateDstu3Test {
} }
private static String ourConditionalUrl;
@Before
public void before() {
ourConditionalUrl = null;
ourId = null;
}
private static IdType ourId;
public static class PatientProvider implements IResourceProvider { public static class PatientProvider implements IResourceProvider {
@ -125,11 +185,13 @@ public class UpdateDstu3Test {
} }
@Update() @Update()
public MethodOutcome updatePatient(@IdParam IdType theId, @ResourceParam Patient thePatient) { public MethodOutcome updatePatient(@IdParam IdType theId, @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
IdType id = theId.withVersion(thePatient.getIdentifierFirstRep().getValue()); ourId = theId;
ourConditionalUrl = theConditionalUrl;
IdType id = theId != null ? theId.withVersion(thePatient.getIdentifierFirstRep().getValue()) : new IdType("Patient/1");
OperationOutcome oo = new OperationOutcome(); OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDiagnostics("OODETAILS"); oo.addIssue().setDiagnostics("OODETAILS");
if (theId.getValueAsString().contains("CREATE")) { if (id.getValueAsString().contains("CREATE")) {
return new MethodOutcome(id, oo, true); return new MethodOutcome(id, oo, true);
} }

View File

@ -181,6 +181,11 @@
count would not appear in the self/prev/next links and would not actually be applied count would not appear in the self/prev/next links and would not actually be applied
to the search results by the server. Thanks to Jim Steele for letting us know! to the search results by the server. Thanks to Jim Steele for letting us know!
</action> </action>
<action type="fix">
Conditional update on server failed to process if the conditional URL did not have any
search parameters that did not start with an underscore. E.g. "Patient?_id=1" failed
even though this is a valid conditional reference.
</action>
</release> </release>
<release version="1.5" date="2016-04-20"> <release version="1.5" date="2016-04-20">
<action type="fix" issue="339"> <action type="fix" issue="339">