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.HttpServletResponse;
import org.hl7.fhir.dstu3.model.IdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
@ -108,6 +110,23 @@ public List<Organization> getAllOrganizations() {
}
//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
@Search

View File

@ -180,7 +180,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
final ResourceTable entity = readEntityLatestVersion(theId);
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);
@ -1038,7 +1038,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
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())) {
@ -1080,7 +1080,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entityToUpdate = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart()) {
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.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.hl7.fhir.instance.model.api.IBaseResource;
import com.github.dnault.xmlpatch.Patcher;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class XmlPatchUtils {
@ -23,12 +23,12 @@ public class XmlPatchUtils {
ByteArrayOutputStream result = new ByteArrayOutputStream();
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) {
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);
return retVal;

View File

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

View File

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

View File

@ -85,9 +85,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testSearchPagingKeepsOldSearches() throws Exception {
String methodName = "testSearchPagingKeepsOldSearches";
@ -108,13 +105,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
List<String> linkNext = Lists.newArrayList();
for (int i = 0; i < 100; i++) {
Bundle bundle = ourClient
.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches"))
.count(5)
.returnBundle(Bundle.class)
.execute();
Bundle bundle = ourClient.search().forResource(Patient.class).where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches")).count(5).returnBundle(Bundle.class).execute();
assertTrue(isNotBlank(bundle.getLink("next").getUrl()));
assertEquals(5, bundle.getEntry().size());
linkNext.add(bundle.getLink("next").getUrl());
@ -207,7 +198,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
CloseableHttpResponse response = ourHttpClient.execute(patch);
try {
assertEquals(412, response.getStatusLine().getStatusCode());
assertEquals(409, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseString, containsString("<OperationOutcome"));
assertThat(responseString, containsString("<diagnostics value=\"Version 9 is not the most recent version of this resource, unable to apply patch\"/>"));
@ -973,7 +964,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response);
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 {
ourClient.read().resource("Patient").withId(id1).execute();
fail();
@ -999,7 +991,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String response = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(response);
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 {
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
public void testUpdateInvalidReference2() throws IOException, Exception {
String methodName = "testUpdateInvalidReference2";

View File

@ -7,6 +7,18 @@
</properties>
<body>
<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">
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
@ -62,6 +74,13 @@
and the new MimeTypes
(e.g. <![CDATA[<code>application/fhir+xml</code>]]>)
</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 version="2.0" date="2016-08-30">
<action type="fix">

View File

@ -236,6 +236,17 @@
<pre><![CDATA[PUT [serverBase]/Patient/123
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" />
</section>