Swap JSON Patch provider
This commit is contained in:
parent
349b16786a
commit
f2e382a132
|
@ -147,6 +147,10 @@
|
|||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
|
@ -182,14 +186,8 @@
|
|||
|
||||
<!-- Patch Dependencies -->
|
||||
<dependency>
|
||||
<groupId>net.riotopsys</groupId>
|
||||
<artifactId>json_patch</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>com.google.code.gson</artifactId>
|
||||
<groupId>gson</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>json-patch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.dnault</groupId>
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import net.riotopsys.json_patch.JsonPath;
|
||||
import net.riotopsys.json_patch.operation.AbsOperation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class CopyOperation extends AbsOperation {
|
||||
|
||||
public JsonPath mySourcePath;
|
||||
|
||||
public CopyOperation(JsonPath theTargetPath, JsonPath theSourcePath) {
|
||||
mySourcePath = theSourcePath;
|
||||
this.path = theTargetPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOperationName() {
|
||||
return "copy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement apply(JsonElement original) {
|
||||
JsonElement result = duplicate(original);
|
||||
|
||||
JsonElement item = path.head().navigate(result);
|
||||
JsonElement data = mySourcePath.head().navigate(original);
|
||||
|
||||
if (item.isJsonObject()) {
|
||||
item.getAsJsonObject().add(path.tail(), data);
|
||||
} else if (item.isJsonArray()) {
|
||||
|
||||
JsonArray array = item.getAsJsonArray();
|
||||
|
||||
int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail());
|
||||
|
||||
List<JsonElement> temp = new ArrayList<JsonElement>();
|
||||
|
||||
Iterator<JsonElement> iter = array.iterator();
|
||||
while (iter.hasNext()) {
|
||||
JsonElement stuff = iter.next();
|
||||
iter.remove();
|
||||
temp.add(stuff);
|
||||
}
|
||||
|
||||
temp.add(index, data);
|
||||
|
||||
for (JsonElement stuff : temp) {
|
||||
array.add(stuff);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,73 +20,47 @@ package ca.uhn.fhir.jpa.util.jsonpatch;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.fge.jsonpatch.JsonPatch;
|
||||
import com.github.fge.jsonpatch.JsonPatchException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.JsonParser;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import net.riotopsys.json_patch.JsonPatch;
|
||||
import net.riotopsys.json_patch.JsonPath;
|
||||
import net.riotopsys.json_patch.operation.AddOperation;
|
||||
import net.riotopsys.json_patch.operation.MoveOperation;
|
||||
import net.riotopsys.json_patch.operation.RemoveOperation;
|
||||
import net.riotopsys.json_patch.operation.ReplaceOperation;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class JsonPatchUtils {
|
||||
|
||||
public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) {
|
||||
JsonPatch parsedPatch = new JsonPatch();
|
||||
|
||||
// Parse the patch
|
||||
Gson gson = JsonParser.newGson();
|
||||
JsonElement jsonElement = gson.fromJson(thePatchBody, JsonElement.class);
|
||||
JsonArray array = jsonElement.getAsJsonArray();
|
||||
for (JsonElement nextElement : array) {
|
||||
JsonObject nextElementAsObject = (JsonObject) nextElement;
|
||||
|
||||
String opName = nextElementAsObject.get("op").getAsString();
|
||||
if ("add".equals(opName)) {
|
||||
AddOperation op = new AddOperation(toPath(nextElementAsObject), nextElementAsObject.get("value"));
|
||||
parsedPatch.add(op);
|
||||
} else if ("remove".equals(opName)) {
|
||||
RemoveOperation op = new RemoveOperation(toPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else if ("replace".equals(opName)) {
|
||||
ReplaceOperation op = new ReplaceOperation(toPath(nextElementAsObject), nextElementAsObject.get("value"));
|
||||
parsedPatch.add(op);
|
||||
} else if ("copy".equals(opName)) {
|
||||
CopyOperation op = new CopyOperation(toPath(nextElementAsObject), toFromPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else if ("move".equals(opName)) {
|
||||
MoveOperation op = new MoveOperation(toPath(nextElementAsObject), toFromPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else {
|
||||
throw new InvalidRequestException("Invalid JSON PATCH operation: " + opName);
|
||||
}
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false);
|
||||
|
||||
JsonFactory factory = mapper.getFactory();
|
||||
|
||||
final JsonPatch patch;
|
||||
try {
|
||||
com.fasterxml.jackson.core.JsonParser parser = factory.createParser(thePatchBody);
|
||||
JsonNode jsonPatchNode = mapper.readTree(parser);
|
||||
patch = JsonPatch.fromJson(jsonPatchNode);
|
||||
|
||||
JsonNode originalJsonDocument = mapper.readTree(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate));
|
||||
JsonNode after = patch.apply(originalJsonDocument);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
|
||||
|
||||
T retVal = theCtx.newJsonParser().parseResource(clazz, mapper.writeValueAsString(after));
|
||||
return retVal;
|
||||
|
||||
} catch (IOException | JsonPatchException theE) {
|
||||
throw new InvalidRequestException(theE.getMessage());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
|
||||
|
||||
JsonElement originalJsonDocument = gson.fromJson(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate), JsonElement.class);
|
||||
JsonElement target = parsedPatch.apply(originalJsonDocument);
|
||||
T retVal = theCtx.newJsonParser().parseResource(clazz, gson.toJson(target));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static JsonPath toFromPath(JsonObject nextElementAsObject) {
|
||||
return new JsonPath(nextElementAsObject.get("from").getAsString());
|
||||
}
|
||||
|
||||
private static JsonPath toPath(JsonObject nextElementAsObject) {
|
||||
return new JsonPath(nextElementAsObject.get("path").getAsString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2198,131 +2198,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatch() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatchWithContentionCheckBad() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatchWithContentionCheckBad";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader("If-Match", "W/\"9\"");
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
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\"/>"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("1", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(true, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatchWithContentionCheckGood() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatchWithContentionCheckGood";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader("If-Match", "W/\"1\"");
|
||||
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingXmlPatch() throws Exception {
|
||||
String methodName = "testPatchUsingXmlPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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>";
|
||||
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreserveVersionsOnAuditEvent() {
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPatch;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class PatchProviderR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatch() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in an invalid JSON Patch and make sure the error message
|
||||
* that is returned is useful
|
||||
*/
|
||||
@Test
|
||||
public void testPatchUsingJsonPatchInvalid() throws Exception {
|
||||
IIdType id;
|
||||
{
|
||||
Observation patient = new Observation();
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
id = myObservationDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
// Quotes are incorrect in the "value" body
|
||||
String patchText = "[ {\n" +
|
||||
" \"comment\": \"add image to examination\",\n" +
|
||||
" \"patch\": [ {\n" +
|
||||
" \"op\": \"add\",\n" +
|
||||
" \"path\": \"/derivedFrom/-\",\n" +
|
||||
" \"value\": [{'reference': '/Media/465eb73a-bce3-423a-b86e-5d0d267638f4'}]\n" +
|
||||
" } ]\n" +
|
||||
" } ]";
|
||||
|
||||
|
||||
HttpPatch patch = new HttpPatch(ourServerBase + "/Observation/" + id.getIdPart());
|
||||
patch.setEntity(new StringEntity(patchText, ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("was expecting double-quote to start field name"));
|
||||
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatchWithContentionCheckBad() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatchWithContentionCheckBad";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader("If-Match", "W/\"9\"");
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
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\"/>"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("1", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(true, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatchWithContentionCheckGood() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatchWithContentionCheckGood";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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.addHeader("If-Match", "W/\"1\"");
|
||||
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingXmlPatch() throws Exception {
|
||||
String methodName = "testPatchUsingXmlPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
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>";
|
||||
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
|
||||
patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class JsonPatchUtilsTest extends BaseJpaTest {
|
||||
|
||||
public static final FhirContext ourCtx = FhirContext.forR4();
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(JsonPatchUtilsTest.class);
|
||||
|
||||
@Test
|
||||
public void testInvalidPatchJsonError() {
|
||||
|
||||
// Quotes are incorrect in the "value" body
|
||||
String patchText = "[ {\n" +
|
||||
" \"comment\": \"add image to examination\",\n" +
|
||||
" \"patch\": [ {\n" +
|
||||
" \"op\": \"add\",\n" +
|
||||
" \"path\": \"/derivedFrom/-\",\n" +
|
||||
" \"value\": [{'reference': '/Media/465eb73a-bce3-423a-b86e-5d0d267638f4'}]\n" +
|
||||
" } ]\n" +
|
||||
" } ]";
|
||||
|
||||
try {
|
||||
JsonPatchUtils.apply(ourCtx, new Observation(), patchText);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
ourLog.info(e.toString());
|
||||
assertThat(e.toString(), containsString("was expecting double-quote to start field name"));
|
||||
// The error message should not contain the patch body
|
||||
assertThat(e.toString(), not(containsString("add image to examination")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidPatchSyntaxError() {
|
||||
|
||||
// Quotes are incorrect in the "value" body
|
||||
String patchText = "[ {\n" +
|
||||
" \"comment\": \"add image to examination\",\n" +
|
||||
" \"patch\": [ {\n" +
|
||||
" \"op\": \"foo\",\n" +
|
||||
" \"path\": \"/derivedFrom/-\",\n" +
|
||||
" \"value\": [{\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}]\n" +
|
||||
" } ]\n" +
|
||||
" } ]";
|
||||
|
||||
try {
|
||||
JsonPatchUtils.apply(ourCtx, new Observation(), patchText);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
ourLog.info(e.toString());
|
||||
assertThat(e.toString(), containsString("missing type id property 'op'"));
|
||||
// The error message should not contain the patch body
|
||||
assertThat(e.toString(), not(containsString("add image to examination")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FhirContext getContext() {
|
||||
return ourCtx;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlatformTransactionManager getTxManager() {
|
||||
return null;
|
||||
}
|
||||
}
|
10
pom.xml
10
pom.xml
|
@ -655,6 +655,11 @@
|
|||
<artifactId>xml-patch</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>json-patch</artifactId>
|
||||
<version>1.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
|
@ -840,11 +845,6 @@
|
|||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.riotopsys</groupId>
|
||||
<artifactId>json_patch</artifactId>
|
||||
<version>0.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.json-lib</groupId>
|
||||
<artifactId>json-lib</artifactId>
|
||||
|
|
|
@ -164,6 +164,13 @@
|
|||
channel is destroyed (because its subscription is deleted) then the remove() method will be called on that
|
||||
channel.
|
||||
</action>
|
||||
<action type="change">
|
||||
The JSON Patch provider has been switched to use the provider from the
|
||||
<![CDATA[
|
||||
<a href="https://github.com/java-json-tools/json-patch">Java JSON Tools</a>
|
||||
]]>
|
||||
project, as it is much more robust and fault tolerant.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.7.0" date="2019-02-06" description="Gale">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue