Patch modifications

This commit is contained in:
James Agnew 2016-09-18 08:35:54 -04:00
parent 40286f49c2
commit d8c99363db
8 changed files with 133 additions and 50 deletions

View File

@ -10,6 +10,8 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.hl7.fhir.dstu3.model.IdType;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
@ -108,6 +110,23 @@ public List<Organization> getAllOrganizations() {
} }
//END SNIPPET: searchAll //END SNIPPET: searchAll
//START SNIPPET: updateEtag
@Update
public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
String resourceId = theId.getIdPart();
String versionId = theId.getVersionIdPart(); // this will contain the ETag
String currentVersion = "1"; // populate this with the current version
if (!versionId.equals(currentVersion)) {
throw new ResourceVersionConflictException("Expected version " + currentVersion);
}
// ... perform the update ...
return new MethodOutcome();
}
//END SNIPPET: updateEtag
//START SNIPPET: summaryAndElements //START SNIPPET: summaryAndElements
@Search @Search

View File

@ -180,7 +180,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
final ResourceTable entity = readEntityLatestVersion(theId); final ResourceTable entity = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) { if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new InvalidRequestException("Trying to delete " + theId + " but this is not the current version"); throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
} }
validateOkToDelete(deleteConflicts, entity); validateOkToDelete(deleteConflicts, entity);
@ -1038,7 +1038,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
if (resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) != entity.getVersion()) { if (resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) != entity.getVersion()) {
throw new InvalidRequestException("Trying to update " + resourceId + " but this is not the current version"); throw new ResourceVersionConflictException("Trying to update " + resourceId + " but this is not the current version");
} }
if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) { if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) {
@ -1080,7 +1080,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entityToUpdate = readEntityLatestVersion(theId); ResourceTable entityToUpdate = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart()) { if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new PreconditionFailedException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
} }
} }

View File

@ -3,13 +3,13 @@ package ca.uhn.fhir.jpa.util.xmlpatch;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import com.github.dnault.xmlpatch.Patcher; import com.github.dnault.xmlpatch.Patcher;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class XmlPatchUtils { public class XmlPatchUtils {
@ -23,12 +23,12 @@ public class XmlPatchUtils {
ByteArrayOutputStream result = new ByteArrayOutputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream();
try { try {
Patcher.patch(new ByteArrayInputStream(inputResource.getBytes(StandardCharsets.UTF_8)), new ByteArrayInputStream(thePatchBody.getBytes(StandardCharsets.UTF_8)), result); Patcher.patch(new ByteArrayInputStream(inputResource.getBytes(Constants.CHARSET_UTF8)), new ByteArrayInputStream(thePatchBody.getBytes(Constants.CHARSET_UTF8)), result);
} catch (IOException e) { } catch (IOException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
String resultString = new String(result.toByteArray(), StandardCharsets.UTF_8); String resultString = new String(result.toByteArray(), Constants.CHARSET_UTF8);
T retVal = theCtx.newXmlParser().parseResource(clazz, resultString); T retVal = theCtx.newXmlParser().parseResource(clazz, resultString);
return retVal; return retVal;

View File

@ -880,7 +880,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
try { try {
myPatientDao.delete(id2, mySrd); myPatientDao.delete(id2, mySrd);
fail(); fail();
} catch (InvalidRequestException e) { } catch (ResourceVersionConflictException e) {
// good // good
} }

View File

@ -1046,7 +1046,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
try { try {
myPatientDao.delete(id2, mySrd); myPatientDao.delete(id2, mySrd);
fail(); fail();
} catch (InvalidRequestException e) { } catch (ResourceVersionConflictException e) {
// good // good
} }

View File

@ -85,9 +85,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();
} }
@Test @Test
public void testSearchPagingKeepsOldSearches() throws Exception { public void testSearchPagingKeepsOldSearches() throws Exception {
String methodName = "testSearchPagingKeepsOldSearches"; String methodName = "testSearchPagingKeepsOldSearches";
@ -105,16 +102,10 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
patient.addName().addFamily(methodName).addGiven("Joe"); patient.addName().addFamily(methodName).addGiven("Joe");
myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
} }
List<String> linkNext = Lists.newArrayList(); List<String> linkNext = Lists.newArrayList();
for (int i = 0 ; i < 100; i++) { for (int i = 0; i < 100; i++) {
Bundle bundle = ourClient Bundle bundle = ourClient.search().forResource(Patient.class).where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches")).count(5).returnBundle(Bundle.class).execute();
.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches"))
.count(5)
.returnBundle(Bundle.class)
.execute();
assertTrue(isNotBlank(bundle.getLink("next").getUrl())); assertTrue(isNotBlank(bundle.getLink("next").getUrl()));
assertEquals(5, bundle.getEntry().size()); assertEquals(5, bundle.getEntry().size());
linkNext.add(bundle.getLink("next").getUrl()); linkNext.add(bundle.getLink("next").getUrl());
@ -142,22 +133,22 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
CloseableHttpResponse response = ourHttpClient.execute(patch); CloseableHttpResponse response = ourHttpClient.execute(patch);
try { try {
assertEquals(200, response.getStatusLine().getStatusCode()); assertEquals(200, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseString, containsString("<OperationOutcome")); assertThat(responseString, containsString("<OperationOutcome"));
assertThat(responseString, containsString("INFORMATION")); assertThat(responseString, containsString("INFORMATION"));
} finally { } finally {
response.close(); response.close();
} }
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart()); assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive()); assertEquals(false, newPt.getActive());
} }
@Test @Test
public void testPatchUsingJsonPatchWithContentionCheckGood() throws Exception { public void testPatchUsingJsonPatchWithContentionCheckGood() throws Exception {
String methodName = "testPatchUsingJsonPatchWithContentionCheckGood"; String methodName = "testPatchUsingJsonPatchWithContentionCheckGood";
@ -173,17 +164,17 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
patch.addHeader("If-Match", "W/\"1\""); patch.addHeader("If-Match", "W/\"1\"");
CloseableHttpResponse response = ourHttpClient.execute(patch); CloseableHttpResponse response = ourHttpClient.execute(patch);
try { try {
assertEquals(200, response.getStatusLine().getStatusCode()); assertEquals(200, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseString, containsString("<OperationOutcome")); assertThat(responseString, containsString("<OperationOutcome"));
assertThat(responseString, containsString("INFORMATION")); assertThat(responseString, containsString("INFORMATION"));
} finally { } finally {
response.close(); response.close();
} }
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart()); assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive()); assertEquals(false, newPt.getActive());
@ -204,17 +195,17 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
patch.addHeader("If-Match", "W/\"9\""); patch.addHeader("If-Match", "W/\"9\"");
CloseableHttpResponse response = ourHttpClient.execute(patch); CloseableHttpResponse response = ourHttpClient.execute(patch);
try { try {
assertEquals(412, response.getStatusLine().getStatusCode()); assertEquals(409, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseString, containsString("<OperationOutcome")); assertThat(responseString, containsString("<OperationOutcome"));
assertThat(responseString, containsString("<diagnostics value=\"Version 9 is not the most recent version of this resource, unable to apply patch\"/>")); assertThat(responseString, containsString("<diagnostics value=\"Version 9 is not the most recent version of this resource, unable to apply patch\"/>"));
} finally { } finally {
response.close(); response.close();
} }
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("1", newPt.getIdElement().getVersionIdPart()); assertEquals("1", newPt.getIdElement().getVersionIdPart());
assertEquals(true, newPt.getActive()); assertEquals(true, newPt.getActive());
@ -235,17 +226,17 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart()); HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
String patchString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><diff xmlns:fhir=\"http://hl7.org/fhir\"><replace sel=\"fhir:Patient/fhir:active/@value\">false</replace></diff>"; String patchString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><diff xmlns:fhir=\"http://hl7.org/fhir\"><replace sel=\"fhir:Patient/fhir:active/@value\">false</replace></diff>";
patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX))); patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
CloseableHttpResponse response = ourHttpClient.execute(patch); CloseableHttpResponse response = ourHttpClient.execute(patch);
try { try {
assertEquals(200, response.getStatusLine().getStatusCode()); assertEquals(200, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseString, containsString("<OperationOutcome")); assertThat(responseString, containsString("<OperationOutcome"));
assertThat(responseString, containsString("INFORMATION")); assertThat(responseString, containsString("INFORMATION"));
} finally { } finally {
response.close(); response.close();
} }
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart()); assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive()); assertEquals(false, newPt.getActive());
@ -306,7 +297,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
patient.getName().get(0).getFamily().get(0).setValue(methodName + "_i"); patient.getName().get(0).getFamily().get(0).setValue(methodName + "_i");
ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue()); ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue());
} }
List<String> idValues; List<String> idValues;
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient/" + id.getIdPart() + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient/" + id.getIdPart() + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3)));
@ -317,7 +308,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3)));
assertThat(idValues.toString(), idValues, contains(ids.get(2), ids.get(1), ids.get(0))); assertThat(idValues.toString(), idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt2060"); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/_history?_at=gt2060");
assertThat(idValues.toString(), idValues, empty()); assertThat(idValues.toString(), idValues, empty());
@ -572,13 +563,13 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
org.setName("ORG"); org.setName("ORG");
IIdType orgId = ourClient.create().resource(org).execute().getId(); IIdType orgId = ourClient.create().resource(org).execute().getId();
assertEquals("1", orgId.getVersionIdPart()); assertEquals("1", orgId.getVersionIdPart());
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.getManagingOrganization().setReference(orgId.toUnqualified().getValue()); patient.getManagingOrganization().setReference(orgId.toUnqualified().getValue());
IIdType patientId = ourClient.create().resource(patient).execute().getId(); IIdType patientId = ourClient.create().resource(patient).execute().getId();
assertEquals("1", patientId.getVersionIdPart()); assertEquals("1", patientId.getVersionIdPart());
AuditEvent ae = new org.hl7.fhir.dstu3.model.AuditEvent(); AuditEvent ae = new org.hl7.fhir.dstu3.model.AuditEvent();
ae.addEntity().getReference().setReference(patientId.toUnqualified().getValue()); ae.addEntity().getReference().setReference(patientId.toUnqualified().getValue());
IIdType aeId = ourClient.create().resource(ae).execute().getId(); IIdType aeId = ourClient.create().resource(ae).execute().getId();
@ -587,11 +578,11 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
patient = ourClient.read().resource(Patient.class).withId(patientId).execute(); patient = ourClient.read().resource(Patient.class).withId(patientId).execute();
assertTrue(patient.getManagingOrganization().getReferenceElement().hasIdPart()); assertTrue(patient.getManagingOrganization().getReferenceElement().hasIdPart());
assertFalse(patient.getManagingOrganization().getReferenceElement().hasVersionIdPart()); assertFalse(patient.getManagingOrganization().getReferenceElement().hasVersionIdPart());
ae = ourClient.read().resource(AuditEvent.class).withId(aeId).execute(); ae = ourClient.read().resource(AuditEvent.class).withId(aeId).execute();
assertTrue(ae.getEntityFirstRep().getReference().getReferenceElement().hasIdPart()); assertTrue(ae.getEntityFirstRep().getReference().getReferenceElement().hasIdPart());
assertTrue(ae.getEntityFirstRep().getReference().getReferenceElement().hasVersionIdPart()); assertTrue(ae.getEntityFirstRep().getReference().getReferenceElement().hasVersionIdPart());
} }
// private void delete(String theResourceType, String theParamName, String theParamValue) { // private void delete(String theResourceType, String theParamName, String theParamValue) {
@ -703,7 +694,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt); String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
ourLog.info("Input: {}", resource); ourLog.info("Input: {}", resource);
HttpPost post = new HttpPost(ourServerBase + "/Patient"); HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post); CloseableHttpResponse response = ourHttpClient.execute(post);
@ -721,7 +712,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals("1", id.getVersionIdPart()); assertEquals("1", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart()); assertNotEquals("AAA", id.getIdPart());
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart()); HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
response = ourHttpClient.execute(get); response = ourHttpClient.execute(get);
try { try {
@ -745,7 +736,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt); String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
ourLog.info("Input: {}", resource); ourLog.info("Input: {}", resource);
HttpPost post = new HttpPost(ourServerBase + "/Patient"); HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post); CloseableHttpResponse response = ourHttpClient.execute(post);
@ -763,7 +754,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals("1", id.getVersionIdPart()); assertEquals("1", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart()); assertNotEquals("AAA", id.getIdPart());
HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart() + "/_history/1"); HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart() + "/_history/1");
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put); response = ourHttpClient.execute(put);
@ -780,7 +771,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals("2", id.getVersionIdPart()); assertEquals("2", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart()); assertNotEquals("AAA", id.getIdPart());
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart()); HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
response = ourHttpClient.execute(get); response = ourHttpClient.execute(get);
try { try {
@ -973,7 +964,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response); String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response);
ourLog.info(encoded); ourLog.info(encoded);
assertThat(encoded, containsString("<issue><severity value=\"information\"/><code value=\"informational\"/><diagnostics value=\"Successfully deleted Patient?identifier=testDeleteConditionalMultiple resource(s) in 2ms\"/></issue>")); assertThat(encoded, containsString(
"<issue><severity value=\"information\"/><code value=\"informational\"/><diagnostics value=\"Successfully deleted Patient?identifier=testDeleteConditionalMultiple resource(s) in 2ms\"/></issue>"));
try { try {
ourClient.read().resource("Patient").withId(id1).execute(); ourClient.read().resource("Patient").withId(id1).execute();
fail(); fail();
@ -991,7 +983,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
@Test @Test
public void testDeleteConditionalNoMatches() throws Exception { public void testDeleteConditionalNoMatches() throws Exception {
String methodName = "testDeleteConditionalNoMatches"; String methodName = "testDeleteConditionalNoMatches";
HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?identifier=" + methodName); HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?identifier=" + methodName);
CloseableHttpResponse resp = ourHttpClient.execute(delete); CloseableHttpResponse resp = ourHttpClient.execute(delete);
try { try {
@ -999,7 +991,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String response = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); String response = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(response); ourLog.info(response);
assertEquals(200, resp.getStatusLine().getStatusCode()); assertEquals(200, resp.getStatusLine().getStatusCode());
assertThat(response, containsString("<issue><severity value=\"warning\"/><code value=\"not-found\"/><diagnostics value=\"Unable to find resource matching URL &quot;Patient?identifier=testDeleteConditionalNoMatches&quot;. Deletion failed.\"/></issue>")); assertThat(response, containsString(
"<issue><severity value=\"warning\"/><code value=\"not-found\"/><diagnostics value=\"Unable to find resource matching URL &quot;Patient?identifier=testDeleteConditionalNoMatches&quot;. Deletion failed.\"/></issue>"));
} finally { } finally {
IOUtils.closeQuietly(resp); IOUtils.closeQuietly(resp);
} }
@ -2648,6 +2641,47 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
} }
} }
@Test
public void testUpdateWithETag() throws IOException, Exception {
String methodName = "testUpdateWithETag";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
IIdType id = ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless();
pt.addName().addFamily("FAM2");
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart());
put.addHeader(Constants.HEADER_IF_MATCH, "W/\"44\"");
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(put);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseString);
assertEquals(409, response.getStatusLine().getStatusCode());
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Trying to update Patient/" + id.getIdPart() + "/_history/44 but this is not the current version"));
} finally {
response.close();
}
// Now a good one
put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart());
put.addHeader(Constants.HEADER_IF_MATCH, "W/\"1\"");
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseString);
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
}
@Test @Test
public void testUpdateInvalidReference2() throws IOException, Exception { public void testUpdateInvalidReference2() throws IOException, Exception {
String methodName = "testUpdateInvalidReference2"; String methodName = "testUpdateInvalidReference2";
@ -2994,7 +3028,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
patient.addCommunication().setPreferred(true); // missing language patient.addCommunication().setPreferred(true); // missing language
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "/$validate"); HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "/$validate");
CloseableHttpResponse response = ourHttpClient.execute(get); CloseableHttpResponse response = ourHttpClient.execute(get);
try { try {

View File

@ -7,6 +7,18 @@
</properties> </properties>
<body> <body>
<release version="2.1" date="TBD"> <release version="2.1" date="TBD">
<action type="add">
Client, Server, and JPA server now support experimental support
for
<![CDATA[HTTP PATCH]]>
using the XML Patch and JSON Patch syntax as explored during the
September 2016 Baltimore Connectathon. See
<![CDATA[<a href="http://wiki.hl7.org/index.php?title=201609_PATCH_Connectathon_Track_Proposal">this wiki page</a>]]>
for a description of the syntax.
<![CDATA[<br/>]]>
Thanks to Pater Girard for all of his help during the connectathon
in implementing this feature!
</action>
<action type="fix"> <action type="fix">
In server, when returning a list of resources, the server sometimes failed to add In server, when returning a list of resources, the server sometimes failed to add
<![CDATA[<code>_include</code>]]> resources to the response bundle if they were <![CDATA[<code>_include</code>]]> resources to the response bundle if they were
@ -62,6 +74,13 @@
and the new MimeTypes and the new MimeTypes
(e.g. <![CDATA[<code>application/fhir+xml</code>]]>) (e.g. <![CDATA[<code>application/fhir+xml</code>]]>)
</action> </action>
<action type="fix">
JPA server now sends correct
<![CDATA[<code>HTTP 409 Version Conflict</code>]]>
when a
DELETE fails because of constraint issues, instead of
<![CDATA[<code>HTTP 400 Invalid Request</code>]]>
</action>
</release> </release>
<release version="2.0" date="2016-08-30"> <release version="2.0" date="2016-08-30">
<action type="fix"> <action type="fix">

View File

@ -236,6 +236,17 @@
<pre><![CDATA[PUT [serverBase]/Patient/123 <pre><![CDATA[PUT [serverBase]/Patient/123
If-Match: W/"3"]]></pre> If-Match: W/"3"]]></pre>
<p>
If a client performs a contention aware update, the ETag version will be
placed in the version part of the IdDt/IdType that is passed into the
method. For example:
</p>
<macro name="snippet">
<param name="id" value="updateEtag" />
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<a name="instance_delete" /> <a name="instance_delete" />
</section> </section>