Jr 20230502 fhir patch test coverage (#4841)

* add tests for fhir patch

* extract constants

* extract some methods

* isolate add operation

* isolate delete operation

* isolate replace operation

* isolate move operation

* clean up integer handling

* enumeration creation

* improve variable naming

* code review feedback
This commit is contained in:
JasonRoberts-smile 2023-05-11 16:40:57 -04:00 committed by GitHub
parent 9d34e6b248
commit 0475cb682f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 480 additions and 224 deletions

View File

@ -129,6 +129,15 @@ public class ParametersUtil {
return getParameterPartValue(theCtx, theParameter, theParameterName).map(t -> (IPrimitiveType<?>) t).map(t -> t.getValueAsString()).orElse(null);
}
public static Optional<Integer> getParameterPartValueAsInteger(FhirContext theCtx, IBase theParameter, String theParameterName) {
return getParameterPartValue(theCtx, theParameter, theParameterName)
.filter(t -> IPrimitiveType.class.isAssignableFrom(t.getClass()))
.map(t -> (IPrimitiveType<?>) t)
.map(IPrimitiveType::getValue)
.filter(t -> Integer.class.isAssignableFrom(t.getClass()))
.map(t -> (Integer) t);
}
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
List<T> retVal = new ArrayList<>();

View File

@ -45,6 +45,16 @@
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.jpa.patch;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -21,8 +23,11 @@ import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.util.XmlExpectationsHelper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -33,6 +38,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class FhirPatchApplyR4Test {
@ -89,6 +95,35 @@ public class FhirPatchApplyR4Test {
}
}
@Test
public void testInsertToInvalidIndex_minimum() {
FhirPatch svc = new FhirPatch(ourCtx);
Patient patient = new Patient();
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent operation = patch.addParameter();
operation.setName("operation");
operation
.addPart()
.setName("type")
.setValue(new CodeType("insert"));
operation
.addPart()
.setName("path")
.setValue(new StringType("Patient.identifier"));
operation
.addPart()
.setName("index")
.setValue(new IntegerType(-1));
try {
svc.apply(patient, patch);
} catch (InvalidRequestException e) {
assertEquals(Msg.code(1270) + "Invalid insert index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
}
}
@Test
public void testMoveFromInvalidIndex() {
FhirPatch svc = new FhirPatch(ourCtx);
@ -122,6 +157,39 @@ public class FhirPatchApplyR4Test {
}
}
@Test
public void testMoveFromInvalidIndex_minimum() {
FhirPatch svc = new FhirPatch(ourCtx);
Patient patient = new Patient();
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent operation = patch.addParameter();
operation.setName("operation");
operation
.addPart()
.setName("type")
.setValue(new CodeType("move"));
operation
.addPart()
.setName("path")
.setValue(new StringType("Patient.identifier"));
operation
.addPart()
.setName("source")
.setValue(new IntegerType(-1));
operation
.addPart()
.setName("destination")
.setValue(new IntegerType(1));
try {
svc.apply(patient, patch);
} catch (InvalidRequestException e) {
assertEquals(Msg.code(1268) + "Invalid move source index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
}
}
@Test
public void testMoveToInvalidIndex() {
FhirPatch svc = new FhirPatch(ourCtx);
@ -156,6 +224,40 @@ public class FhirPatchApplyR4Test {
}
}
@Test
public void testMoveToInvalidIndex_minimum() {
FhirPatch svc = new FhirPatch(ourCtx);
Patient patient = new Patient();
patient.addIdentifier().setSystem("sys");
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent operation = patch.addParameter();
operation.setName("operation");
operation
.addPart()
.setName("type")
.setValue(new CodeType("move"));
operation
.addPart()
.setName("path")
.setValue(new StringType("Patient.identifier"));
operation
.addPart()
.setName("source")
.setValue(new IntegerType(0));
operation
.addPart()
.setName("destination")
.setValue(new IntegerType(-1));
try {
svc.apply(patient, patch);
} catch (InvalidRequestException e) {
assertEquals(Msg.code(1269) + "Invalid move destination index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
}
}
@Test
public void testDeleteItemWithExtension() {
FhirPatch svc = new FhirPatch(ourCtx);
@ -496,4 +598,49 @@ public class FhirPatchApplyR4Test {
}
}
// This implements the official HL7 test suite, as defined in http://hl7.org/fhir/R4/test-cases.zip. There may be some overlap with the cases above.
@ParameterizedTest
@CsvSource(textBlock = """
No Difference, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>
Replace Primitive, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part><part><name value="value"/><valueDate value="1930-01-01"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1930-01-01"/></Patient>
Delete Primitive, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
Add Primitive, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="birthDate"/></part><part><name value="value"/><valueDate value="1930-01-01"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1930-01-01"/></Patient>
Delete Primitive #2, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
Replace Nested Primitive #1, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.contact[0].gender"/></part><part><name value="value"/><valueCode value="female"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="female"/></contact></Patient>
Replace Nested Primitive #2, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.contact[0].name.text"/></part><part><name value="value"/><valueString value="the name"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="the name"/></name><gender value="male"/></contact></Patient>
Delete Nested Primitive #1, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0].gender"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>
Delete Nested Primitive #2, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0].name.text"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><gender value="male"/></contact></Patient>
Add Nested Primitive, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient.contact[0]"/></part><part><name value="name"/><valueString value="gender"/></part><part><name value="value"/><valueCode value="male"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>
Add Complex, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="maritalStatus"/></part><part><name value="value"/><valueCodeableConcept><text value="married"/></valueCodeableConcept></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><maritalStatus><text value="married"/></maritalStatus></Patient>
Replace Complex, <Patient xmlns="http://hl7.org/fhir"><maritalStatus id="1"><text value="married"/></maritalStatus></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.maritalStatus"/></part><part><name value="value"/><valueCodeableConcept id="2"><text value="not married"/></valueCodeableConcept></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><maritalStatus id="2"><text value="not married"/></maritalStatus></Patient>
Delete Complex, <Patient xmlns="http://hl7.org/fhir"><maritalStatus><text value="married"/></maritalStatus></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.maritalStatus"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
Add Anonymous Type, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="contact"/></part><part><name value="value"/><part><name value="name"/><valueHumanName><text value="a name"/></valueHumanName></part></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>
Delete Anonymous Type, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
List unchanged, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
List unchanged contents changed, <Patient xmlns="http://hl7.org/fhir"><identifier id="a"><system value="http://example.org"/><value value="value 1"/></identifier><identifier id="b"><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.identifier[0].value"/></part><part><name value="value"/><valueString value="value 2"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.identifier[1].value"/></part><part><name value="value"/><valueString value="value 1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier id="a"><system value="http://example.org"/><value value="value 2"/></identifier><identifier id="b"><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
Add to list, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="2"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Insert in list #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="1"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
Insert in list #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="0"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
Delete from List #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[0]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Delete from List #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[1]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Delete from List #3, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[2]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
Reorder List #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Reorder List #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Reorder List #3, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="2"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
Reorder List #4, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="0"/></part><part><name value="destination"/><valueInteger value="3"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
Reorder List #5, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="1"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="2"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>
Reorder List #6, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="2"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
""")
public void testHL7Cases(String theName, String theInputResource, String thePatch, String theOutputResource) throws Exception {
IParser parser = ourCtx.newXmlParser();
Patient patient = parser.parseResource(Patient.class, theInputResource);
Parameters parameters = parser.parseResource(Parameters.class, thePatch);
Patient expectedPatient = parser.parseResource(Patient.class, theOutputResource);
FhirPatch svc = new FhirPatch(ourCtx);
svc.apply(patient, parameters);
new XmlExpectationsHelper().assertXmlEqual(theOutputResource, parser.encodeResourceToString(patient));
assertTrue(expectedPatient.equalsDeep(patient), theName);
}
}

View File

@ -53,6 +53,20 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirPatch {
public static final String OPERATION_ADD = "add";
public static final String OPERATION_DELETE = "delete";
public static final String OPERATION_INSERT = "insert";
public static final String OPERATION_MOVE = "move";
public static final String OPERATION_REPLACE = "replace";
public static final String PARAMETER_DESTINATION = "destination";
public static final String PARAMETER_INDEX = "index";
public static final String PARAMETER_NAME = "name";
public static final String PARAMETER_OPERATION = "operation";
public static final String PARAMETER_PATH = "path";
public static final String PARAMETER_SOURCE = "source";
public static final String PARAMETER_TYPE = "type";
public static final String PARAMETER_VALUE = "value";
private final FhirContext myContext;
private boolean myIncludePreviousValueInDiff;
private Set<EncodeContextPath> myIgnorePaths = Collections.emptySet();
@ -81,232 +95,288 @@ public class FhirPatch {
public void apply(IBaseResource theResource, IBaseResource thePatch) {
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, "operation");
for (IBase nextOp : opParameters) {
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "type");
String path = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "path");
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, nextOp, "value");
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, nextOp, "value");
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, PARAMETER_OPERATION);
for (IBase nextOperation : opParameters) {
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOperation, PARAMETER_TYPE);
type = defaultString(type);
path = defaultString(path);
String containingPath;
String elementName;
Integer removeIndex = null;
Integer insertIndex = null;
if ("delete".equals(type)) {
if (path.endsWith(")")) {
// This is probably a filter, so we're probably dealing with a list
int filterArgsIndex = path.lastIndexOf('('); // Let's hope there aren't nested parentheses
int lastDotIndex = path.lastIndexOf('.', filterArgsIndex); // There might be a dot inside the parentheses, so look to the left of that
int secondLastDotIndex = path.lastIndexOf('.', lastDotIndex-1);
containingPath = path.substring(0, secondLastDotIndex);
elementName = path.substring(secondLastDotIndex + 1, lastDotIndex);
} else if (path.endsWith("]")) {
// This is almost definitely a list
int openBracketIndex = path.lastIndexOf('[');
int lastDotIndex = path.lastIndexOf('.', openBracketIndex);
containingPath = path.substring(0, lastDotIndex);
elementName = path.substring(lastDotIndex + 1, openBracketIndex);
} else {
doDelete(theResource, path);
continue;
}
} else if ("add".equals(type)) {
containingPath = path;
elementName = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "name");
} else if ("replace".equals(type)) {
int lastDot = path.lastIndexOf(".");
containingPath = path.substring(0, lastDot);
elementName = path.substring(lastDot + 1);
} else if ("insert".equals(type)) {
int lastDot = path.lastIndexOf(".");
containingPath = path.substring(0, lastDot);
elementName = path.substring(lastDot + 1);
insertIndex = ParametersUtil
.getParameterPartValue(myContext, nextOp, "index")
.map(t -> (IPrimitiveType<Integer>) t)
.map(t -> t.getValue())
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
} else if ("move".equals(type)) {
int lastDot = path.lastIndexOf(".");
containingPath = path.substring(0, lastDot);
elementName = path.substring(lastDot + 1);
insertIndex = ParametersUtil
.getParameterPartValue(myContext, nextOp, "destination")
.map(t -> (IPrimitiveType<Integer>) t)
.map(t -> t.getValue())
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
removeIndex = ParametersUtil
.getParameterPartValue(myContext, nextOp, "source")
.map(t -> (IPrimitiveType<Integer>) t)
.map(t -> t.getValue())
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
if (OPERATION_DELETE.equals(type)) {
handleDeleteOperation(theResource, nextOperation);
} else if (OPERATION_ADD.equals(type)) {
handleAddOperation(theResource, nextOperation);
} else if (OPERATION_REPLACE.equals(type)) {
handleReplaceOperation(theResource, nextOperation);
} else if (OPERATION_INSERT.equals(type)) {
handleInsertOperation(theResource, nextOperation);
} else if (OPERATION_MOVE.equals(type)) {
handleMoveOperation(theResource, nextOperation);
} else {
throw new InvalidRequestException(Msg.code(1267) + "Unknown patch operation type: " + type);
}
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase next : paths) {
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(next.getClass());
String childName = elementName;
BaseRuntimeChildDefinition childDef = elementDef.getChildByName(childName);
BaseRuntimeElementDefinition<?> childElement;
if (childDef == null) {
childName = elementName + "[x]";
childDef = elementDef.getChildByName(childName);
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
} else {
childElement = childDef.getChildByName(childName);
}
if ("move".equals(type)) {
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
if (removeIndex == null || removeIndex >= existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveSourceIndex", removeIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1268) + msg);
}
IBase newValue = existingValues.remove(removeIndex.intValue());
if (insertIndex == null || insertIndex > existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveDestinationIndex", insertIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1269) + msg);
}
existingValues.add(insertIndex, newValue);
childDef.getMutator().setValue(next, null);
for (IBase nextNewValue : existingValues) {
childDef.getMutator().addValue(next, nextNewValue);
}
continue;
} else if ("delete".equals(type)) {
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
List<IBase> elementsToRemove = myContext.newFhirPath().evaluate(theResource, path, IBase.class);
existingValues.removeAll(elementsToRemove);
childDef.getMutator().setValue(next, null);
for (IBase nextNewValue : existingValues) {
childDef.getMutator().addValue(next, nextNewValue);
}
continue;
}
IBase newValue;
if (valuePartValue.isPresent()) {
newValue = valuePartValue.get();
} else {
newValue = childElement.newInstance();
if (valuePart.isPresent()) {
List<IBase> valuePartParts = myContext.newTerser().getValues(valuePart.get(), "part");
for (IBase nextValuePartPart : valuePartParts) {
String name = myContext.newTerser().getSingleValue(nextValuePartPart, "name", IPrimitiveType.class).map(t -> t.getValueAsString()).orElse(null);
if (isNotBlank(name)) {
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
if (value.isPresent()) {
BaseRuntimeChildDefinition partChildDef = childElement.getChildByName(name);
if (partChildDef == null) {
name = name + "[x]";
partChildDef = childElement.getChildByName(name);
}
partChildDef.getMutator().addValue(newValue, value.get());
}
}
}
}
}
if (IBaseEnumeration.class.isAssignableFrom(childElement.getImplementingClass()) || XhtmlNode.class.isAssignableFrom(childElement.getImplementingClass())) {
// If the compositeElementDef is an IBaseEnumeration, we will use the actual compositeElementDef definition to build one, since
// it needs the right factory object passed to its constructor
IPrimitiveType<?> newValueInstance = (IPrimitiveType<?>) childElement.newInstance();
newValueInstance.setValueAsString(((IPrimitiveType<?>) newValue).getValueAsString());
childDef.getMutator().setValue(next, newValueInstance);
newValue = newValueInstance;
}
if ("insert".equals(type)) {
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
if (insertIndex == null || insertIndex > existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidInsertIndex", insertIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1270) + msg);
}
existingValues.add(insertIndex, newValue);
childDef.getMutator().setValue(next, null);
for (IBase nextNewValue : existingValues) {
childDef.getMutator().addValue(next, nextNewValue);
}
} else if ("add".equals(type)) {
childDef.getMutator().addValue(next, newValue);
} else if ("replace".equals(type)) {
childDef.getMutator().setValue(next, newValue);
}
}
}
}
private void doDelete(IBaseResource theResource, String thePath) {
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, thePath, IBase.class);
for (IBase next : paths) {
myContext.newTerser().visit(next, new IModelVisitor2() {
@Override
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
if (theElement instanceof IPrimitiveType) {
((IPrimitiveType<?>) theElement).setValueAsString(null);
private void handleAddOperation(IBaseResource theResource, IBase theParameters) {
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
String elementName = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_NAME);
String containingPath = defaultString(path);
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase nextElement : containingElements) {
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
childDefinition.getChildDef().getMutator().addValue(nextElement, newValue);
}
}
private void handleInsertOperation(IBaseResource theResource, IBase theParameters) {
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
path = defaultString(path);
int lastDot = path.lastIndexOf(".");
String containingPath = path.substring(0, lastDot);
String elementName = path.substring(lastDot + 1);
Integer insertIndex = ParametersUtil
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_INDEX)
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase nextElement : containingElements) {
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(nextElement));
if (insertIndex == null || insertIndex < 0 || insertIndex > existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidInsertIndex", insertIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1270) + msg);
}
existingValues.add(insertIndex, newValue);
childDefinition.getChildDef().getMutator().setValue(nextElement, null);
for (IBase nextNewValue : existingValues) {
childDefinition.getChildDef().getMutator().addValue(nextElement, nextNewValue);
}
}
}
private void handleDeleteOperation(IBaseResource theResource, IBase theParameters) {
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
path = defaultString(path);
String containingPath;
String elementName;
if (path.endsWith(")")) {
// This is probably a filter, so we're probably dealing with a list
int filterArgsIndex = path.lastIndexOf('('); // Let's hope there aren't nested parentheses
int lastDotIndex = path.lastIndexOf('.', filterArgsIndex); // There might be a dot inside the parentheses, so look to the left of that
int secondLastDotIndex = path.lastIndexOf('.', lastDotIndex-1);
containingPath = path.substring(0, secondLastDotIndex);
elementName = path.substring(secondLastDotIndex + 1, lastDotIndex);
} else if (path.endsWith("]")) {
// This is almost definitely a list
int openBracketIndex = path.lastIndexOf('[');
int lastDotIndex = path.lastIndexOf('.', openBracketIndex);
containingPath = path.substring(0, lastDotIndex);
elementName = path.substring(lastDotIndex + 1, openBracketIndex);
} else {
containingPath = path;
elementName = null;
}
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase nextElement : containingElements) {
if (elementName == null) {
deleteSingleElement(nextElement);
} else {
deleteFromList(theResource, nextElement, elementName, path);
}
}
}
private void deleteFromList(IBaseResource theResource, IBase theContainingElement, String theListElementName, String theElementToDeletePath) {
ChildDefinition childDefinition = findChildDefinition(theContainingElement, theListElementName);
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(theContainingElement));
List<IBase> elementsToRemove = myContext.newFhirPath().evaluate(theResource, theElementToDeletePath, IBase.class);
existingValues.removeAll(elementsToRemove);
childDefinition.getChildDef().getMutator().setValue(theContainingElement, null);
for (IBase nextNewValue : existingValues) {
childDefinition.getChildDef().getMutator().addValue(theContainingElement, nextNewValue);
}
}
private void handleReplaceOperation(IBaseResource theResource, IBase theParameters) {
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
path = defaultString(path);
int lastDot = path.lastIndexOf(".");
String containingPath = path.substring(0, lastDot);
String elementName = path.substring(lastDot + 1);
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase nextElement : containingElements) {
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
childDefinition.getChildDef().getMutator().setValue(nextElement, newValue);
}
}
private void handleMoveOperation(IBaseResource theResource, IBase theParameters) {
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
path = defaultString(path);
int lastDot = path.lastIndexOf(".");
String containingPath = path.substring(0, lastDot);
String elementName = path.substring(lastDot + 1);
Integer insertIndex = ParametersUtil
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_DESTINATION)
.orElseThrow(() -> new InvalidRequestException("No index supplied for move operation"));
Integer removeIndex = ParametersUtil
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_SOURCE)
.orElseThrow(() -> new InvalidRequestException("No index supplied for move operation"));
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase nextElement : containingElements) {
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(nextElement));
if (removeIndex == null || removeIndex < 0 || removeIndex >= existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveSourceIndex", removeIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1268) + msg);
}
IBase newValue = existingValues.remove(removeIndex.intValue());
if (insertIndex == null || insertIndex < 0 || insertIndex > existingValues.size()) {
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveDestinationIndex", insertIndex, path, existingValues.size());
throw new InvalidRequestException(Msg.code(1269) + msg);
}
existingValues.add(insertIndex, newValue);
childDefinition.getChildDef().getMutator().setValue(nextElement, null);
for (IBase nextNewValue : existingValues) {
childDefinition.getChildDef().getMutator().addValue(nextElement, nextNewValue);
}
}
}
private ChildDefinition findChildDefinition(IBase theContainingElement, String theElementName) {
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(theContainingElement.getClass());
String childName = theElementName;
BaseRuntimeChildDefinition childDef = elementDef.getChildByName(childName);
BaseRuntimeElementDefinition<?> childElement;
if (childDef == null) {
childName = theElementName + "[x]";
childDef = elementDef.getChildByName(childName);
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
} else {
childElement = childDef.getChildByName(childName);
}
return new ChildDefinition(childDef, childElement);
}
private IBase getNewValue(IBase theParameters, IBase theElement, ChildDefinition theChildDefinition) {
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, theParameters, PARAMETER_VALUE);
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, theParameters, PARAMETER_VALUE);
IBase newValue;
if (valuePartValue.isPresent()) {
newValue = valuePartValue.get();
} else {
newValue = theChildDefinition.getChildElement().newInstance();
if (valuePart.isPresent()) {
IBase theValueElement = valuePart.get();
populateNewValue(theChildDefinition, newValue, theValueElement);
}
}
if (IBaseEnumeration.class.isAssignableFrom(theChildDefinition.getChildElement().getImplementingClass()) || XhtmlNode.class.isAssignableFrom(theChildDefinition.getChildElement().getImplementingClass())) {
// If the compositeElementDef is an IBaseEnumeration, we will use the actual compositeElementDef definition to build one, since
// it needs the right factory object passed to its constructor
IPrimitiveType<?> newValueInstance;
if (theChildDefinition.getChildDef().getInstanceConstructorArguments() != null) {
newValueInstance = (IPrimitiveType<?>) theChildDefinition.getChildElement().newInstance(
theChildDefinition.getChildDef().getInstanceConstructorArguments());
} else {
newValueInstance = (IPrimitiveType<?>) theChildDefinition.getChildElement().newInstance();
}
newValueInstance.setValueAsString(((IPrimitiveType<?>) newValue).getValueAsString());
theChildDefinition.getChildDef().getMutator().setValue(theElement, newValueInstance);
newValue = newValueInstance;
}
return newValue;
}
private void populateNewValue(ChildDefinition theChildDefinition, IBase theNewValue, IBase theValueElement) {
List<IBase> valuePartParts = myContext.newTerser().getValues(theValueElement, "part");
for (IBase nextValuePartPart : valuePartParts) {
String name = myContext.newTerser().getSingleValue(nextValuePartPart, PARAMETER_NAME, IPrimitiveType.class).map(IPrimitiveType::getValueAsString).orElse(null);
if (isNotBlank(name)) {
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
if (value.isPresent()) {
BaseRuntimeChildDefinition partChildDef = theChildDefinition.getChildElement().getChildByName(name);
if (partChildDef == null) {
name = name + "[x]";
partChildDef = theChildDefinition.getChildElement().getChildByName(name);
}
return true;
partChildDef.getMutator().addValue(theNewValue, value.get());
}
@Override
public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
theNextExt.setUrl(null);
theNextExt.setValue(null);
return true;
}
});
}
}
}
private void deleteSingleElement(IBase theElementToDelete) {
myContext.newTerser().visit(theElementToDelete, new IModelVisitor2() {
@Override
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
if (theElement instanceof IPrimitiveType) {
((IPrimitiveType<?>) theElement).setValueAsString(null);
}
return true;
}
@Override
public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
theNextExt.setUrl(null);
theNextExt.setValue(null);
return true;
}
});
}
public IBaseParameters diff(@Nullable IBaseResource theOldValue, @Nonnull IBaseResource theNewValue) {
IBaseParameters retVal = ParametersUtil.newInstance(myContext);
String newValueTypeName = myContext.getResourceDefinition(theNewValue).getName();
if (theOldValue == null) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, "operation");
ParametersUtil.addPartCode(myContext, operation, "type", "insert");
ParametersUtil.addPartString(myContext, operation, "path", newValueTypeName);
ParametersUtil.addPart(myContext, operation, "value", theNewValue);
IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, newValueTypeName);
ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, theNewValue);
} else {
@ -339,9 +409,9 @@ public class FhirPatch {
BaseRuntimeElementDefinition<?> sourceDef = myContext.getElementDefinition(theOldField.getClass());
BaseRuntimeElementDefinition<?> targetDef = myContext.getElementDefinition(theNewField.getClass());
if (!sourceDef.getName().equals(targetDef.getName())) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
addValueToDiff(operation, theOldField, theNewField);
} else {
if (theOldField instanceof IPrimitiveType) {
@ -350,9 +420,9 @@ public class FhirPatch {
String oldValueAsString = toValue(oldPrimitive);
String newValueAsString = toValue(newPrimitive);
if (!Objects.equals(oldValueAsString, newValueAsString)) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
addValueToDiff(operation, oldPrimitive, newPrimitive);
}
}
@ -405,9 +475,9 @@ public class FhirPatch {
// Find deleted items
while (sourceIndex < sourceValues.size()) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
ParametersUtil.addPartCode(myContext, operation, "type", "delete");
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : ""));
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_DELETE);
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : ""));
sourceIndex++;
targetIndex++;
@ -417,10 +487,10 @@ public class FhirPatch {
}
private void addInsertItems(IBaseParameters theDiff, List<? extends IBase> theTargetValues, int theTargetIndex, String thePath, BaseRuntimeChildDefinition theChildDefinition) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
ParametersUtil.addPartCode(myContext, operation, "type", "insert");
ParametersUtil.addPartString(myContext, operation, "path", thePath);
ParametersUtil.addPartInteger(myContext, operation, "index", theTargetIndex);
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, thePath);
ParametersUtil.addPartInteger(myContext, operation, PARAMETER_INDEX, theTargetIndex);
IBase value = theTargetValues.get(theTargetIndex);
BaseRuntimeElementDefinition<?> valueDef = myContext.getElementDefinition(value.getClass());
@ -432,7 +502,7 @@ public class FhirPatch {
* childen in instead.
*/
if (valueDef.isStandardType()) {
ParametersUtil.addPart(myContext, operation, "value", value);
ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, value);
} else {
for (BaseRuntimeChildDefinition nextChild : valueDef.getChildren()) {
List<IBase> childValues = nextChild.getAccessor().getValues(value);
@ -454,7 +524,7 @@ public class FhirPatch {
}
IBase newValue = massageValueForDiff(theNewValue);
ParametersUtil.addPart(myContext, theOperationPart, "value", newValue);
ParametersUtil.addPart(myContext, theOperationPart, PARAMETER_VALUE, newValue);
}
private boolean pathIsIgnored(EncodeContextPath theSourceEncodeContext) {
@ -469,19 +539,21 @@ public class FhirPatch {
}
private IBase massageValueForDiff(IBase theNewValue) {
IBase massagedValue = theNewValue;
// XHTML content is dealt with by putting it in a string
if (theNewValue instanceof XhtmlNode) {
String xhtmlString = ((XhtmlNode) theNewValue).getValueAsString();
theNewValue = myContext.getElementDefinition("string").newInstance(xhtmlString);
massagedValue = myContext.getElementDefinition("string").newInstance(xhtmlString);
}
// IIdType can hold a fully qualified ID, but we just want the ID part to show up in diffs
if (theNewValue instanceof IIdType) {
String idPart = ((IIdType) theNewValue).getIdPart();
theNewValue = myContext.getElementDefinition("id").newInstance(idPart);
massagedValue = myContext.getElementDefinition("id").newInstance(idPart);
}
return theNewValue;
return massagedValue;
}
private String toValue(IPrimitiveType<?> theOldPrimitive) {
@ -490,4 +562,22 @@ public class FhirPatch {
}
return theOldPrimitive.getValueAsString();
}
private static class ChildDefinition {
private final BaseRuntimeChildDefinition myChildDef;
private final BaseRuntimeElementDefinition<?> myChildElement;
public ChildDefinition(BaseRuntimeChildDefinition theChildDef, BaseRuntimeElementDefinition<?> theChildElement) {
this.myChildDef = theChildDef;
this.myChildElement = theChildElement;
}
public BaseRuntimeChildDefinition getChildDef() {
return myChildDef;
}
public BaseRuntimeElementDefinition<?> getChildElement() {
return myChildElement;
}
}
}