Swap JSON Patch provider

This commit is contained in:
James Agnew 2019-04-24 14:10:21 -04:00
parent 349b16786a
commit f2e382a132
8 changed files with 321 additions and 277 deletions

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false);
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);
}
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();
JsonElement originalJsonDocument = gson.fromJson(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate), JsonElement.class);
JsonElement target = parsedPatch.apply(originalJsonDocument);
T retVal = theCtx.newJsonParser().parseResource(clazz, gson.toJson(target));
T retVal = theCtx.newJsonParser().parseResource(clazz, mapper.writeValueAsString(after));
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());
} catch (IOException | JsonPatchException theE) {
throw new InvalidRequestException(theE.getMessage());
}
}
}

View File

@ -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() {

View File

@ -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());
}
}

View File

@ -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
View File

@ -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>

View File

@ -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">