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); 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) { private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
List<T> retVal = new ArrayList<>(); List<T> retVal = new ArrayList<>();

View File

@ -45,6 +45,16 @@
</exclusions> </exclusions>
<scope>test</scope> <scope>test</scope>
</dependency> </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> </dependencies>
<build> <build>

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.jpa.patch; package ca.uhn.fhir.jpa.patch;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.Type;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.Test; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.test.util.XmlExpectationsHelper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; 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.hasSize;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class FhirPatchApplyR4Test { 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 @Test
public void testMoveFromInvalidIndex() { public void testMoveFromInvalidIndex() {
FhirPatch svc = new FhirPatch(ourCtx); 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 @Test
public void testMoveToInvalidIndex() { public void testMoveToInvalidIndex() {
FhirPatch svc = new FhirPatch(ourCtx); 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 @Test
public void testDeleteItemWithExtension() { public void testDeleteItemWithExtension() {
FhirPatch svc = new FhirPatch(ourCtx); 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 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 final FhirContext myContext;
private boolean myIncludePreviousValueInDiff; private boolean myIncludePreviousValueInDiff;
private Set<EncodeContextPath> myIgnorePaths = Collections.emptySet(); private Set<EncodeContextPath> myIgnorePaths = Collections.emptySet();
@ -81,21 +95,85 @@ public class FhirPatch {
public void apply(IBaseResource theResource, IBaseResource thePatch) { public void apply(IBaseResource theResource, IBaseResource thePatch) {
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, "operation"); List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, PARAMETER_OPERATION);
for (IBase nextOp : opParameters) { for (IBase nextOperation : opParameters) {
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "type"); String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOperation, PARAMETER_TYPE);
String path = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "path");
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, nextOp, "value");
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, nextOp, "value");
type = defaultString(type); type = defaultString(type);
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);
}
}
}
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); path = defaultString(path);
String containingPath; String containingPath;
String elementName; String elementName;
Integer removeIndex = null;
Integer insertIndex = null;
if ("delete".equals(type)) {
if (path.endsWith(")")) { if (path.endsWith(")")) {
// This is probably a filter, so we're probably dealing with a list // 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 filterArgsIndex = path.lastIndexOf('('); // Let's hope there aren't nested parentheses
@ -110,126 +188,158 @@ public class FhirPatch {
containingPath = path.substring(0, lastDotIndex); containingPath = path.substring(0, lastDotIndex);
elementName = path.substring(lastDotIndex + 1, openBracketIndex); elementName = path.substring(lastDotIndex + 1, openBracketIndex);
} else { } else {
doDelete(theResource, path);
continue;
}
} else if ("add".equals(type)) {
containingPath = path; containingPath = path;
elementName = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "name"); elementName = null;
} 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"));
} else {
throw new InvalidRequestException(Msg.code(1267) + "Unknown patch operation type: " + type);
} }
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class); List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
for (IBase next : paths) { for (IBase nextElement : containingElements) {
if (elementName == null) {
deleteSingleElement(nextElement);
} else {
deleteFromList(theResource, nextElement, elementName, path);
}
}
}
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(next.getClass()); private void deleteFromList(IBaseResource theResource, IBase theContainingElement, String theListElementName, String theElementToDeletePath) {
ChildDefinition childDefinition = findChildDefinition(theContainingElement, theListElementName);
String childName = elementName; 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); BaseRuntimeChildDefinition childDef = elementDef.getChildByName(childName);
BaseRuntimeElementDefinition<?> childElement; BaseRuntimeElementDefinition<?> childElement;
if (childDef == null) { if (childDef == null) {
childName = elementName + "[x]"; childName = theElementName + "[x]";
childDef = elementDef.getChildByName(childName); childDef = elementDef.getChildByName(childName);
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next()); childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
} else { } else {
childElement = childDef.getChildByName(childName); childElement = childDef.getChildByName(childName);
} }
if ("move".equals(type)) { return new ChildDefinition(childDef, childElement);
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; private IBase getNewValue(IBase theParameters, IBase theElement, ChildDefinition theChildDefinition) {
} else if ("delete".equals(type)) { Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, theParameters, PARAMETER_VALUE);
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next)); Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, theParameters, PARAMETER_VALUE);
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; IBase newValue;
if (valuePartValue.isPresent()) { if (valuePartValue.isPresent()) {
newValue = valuePartValue.get(); newValue = valuePartValue.get();
} else { } else {
newValue = childElement.newInstance(); newValue = theChildDefinition.getChildElement().newInstance();
if (valuePart.isPresent()) { if (valuePart.isPresent()) {
List<IBase> valuePartParts = myContext.newTerser().getValues(valuePart.get(), "part"); 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) { for (IBase nextValuePartPart : valuePartParts) {
String name = myContext.newTerser().getSingleValue(nextValuePartPart, "name", IPrimitiveType.class).map(t -> t.getValueAsString()).orElse(null); String name = myContext.newTerser().getSingleValue(nextValuePartPart, PARAMETER_NAME, IPrimitiveType.class).map(IPrimitiveType::getValueAsString).orElse(null);
if (isNotBlank(name)) { if (isNotBlank(name)) {
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class); Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
if (value.isPresent()) { if (value.isPresent()) {
BaseRuntimeChildDefinition partChildDef = childElement.getChildByName(name); BaseRuntimeChildDefinition partChildDef = theChildDefinition.getChildElement().getChildByName(name);
if (partChildDef == null) { if (partChildDef == null) {
name = name + "[x]"; name = name + "[x]";
partChildDef = childElement.getChildByName(name); partChildDef = theChildDefinition.getChildElement().getChildByName(name);
} }
partChildDef.getMutator().addValue(newValue, value.get()); partChildDef.getMutator().addValue(theNewValue, value.get());
} }
@ -238,47 +348,8 @@ public class FhirPatch {
} }
} }
} private void deleteSingleElement(IBase theElementToDelete) {
myContext.newTerser().visit(theElementToDelete, new IModelVisitor2() {
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 @Override
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
if (theElement instanceof IPrimitiveType) { if (theElement instanceof IPrimitiveType) {
@ -295,7 +366,6 @@ public class FhirPatch {
} }
}); });
} }
}
public IBaseParameters diff(@Nullable IBaseResource theOldValue, @Nonnull IBaseResource theNewValue) { public IBaseParameters diff(@Nullable IBaseResource theOldValue, @Nonnull IBaseResource theNewValue) {
IBaseParameters retVal = ParametersUtil.newInstance(myContext); IBaseParameters retVal = ParametersUtil.newInstance(myContext);
@ -303,10 +373,10 @@ public class FhirPatch {
if (theOldValue == null) { if (theOldValue == null) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, "operation"); IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, "type", "insert"); ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
ParametersUtil.addPartString(myContext, operation, "path", newValueTypeName); ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, newValueTypeName);
ParametersUtil.addPart(myContext, operation, "value", theNewValue); ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, theNewValue);
} else { } else {
@ -339,9 +409,9 @@ public class FhirPatch {
BaseRuntimeElementDefinition<?> sourceDef = myContext.getElementDefinition(theOldField.getClass()); BaseRuntimeElementDefinition<?> sourceDef = myContext.getElementDefinition(theOldField.getClass());
BaseRuntimeElementDefinition<?> targetDef = myContext.getElementDefinition(theNewField.getClass()); BaseRuntimeElementDefinition<?> targetDef = myContext.getElementDefinition(theNewField.getClass());
if (!sourceDef.getName().equals(targetDef.getName())) { if (!sourceDef.getName().equals(targetDef.getName())) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation"); IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, "type", "replace"); ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath); ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
addValueToDiff(operation, theOldField, theNewField); addValueToDiff(operation, theOldField, theNewField);
} else { } else {
if (theOldField instanceof IPrimitiveType) { if (theOldField instanceof IPrimitiveType) {
@ -350,9 +420,9 @@ public class FhirPatch {
String oldValueAsString = toValue(oldPrimitive); String oldValueAsString = toValue(oldPrimitive);
String newValueAsString = toValue(newPrimitive); String newValueAsString = toValue(newPrimitive);
if (!Objects.equals(oldValueAsString, newValueAsString)) { if (!Objects.equals(oldValueAsString, newValueAsString)) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation"); IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, "type", "replace"); ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath); ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
addValueToDiff(operation, oldPrimitive, newPrimitive); addValueToDiff(operation, oldPrimitive, newPrimitive);
} }
} }
@ -405,9 +475,9 @@ public class FhirPatch {
// Find deleted items // Find deleted items
while (sourceIndex < sourceValues.size()) { while (sourceIndex < sourceValues.size()) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation"); IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, "type", "delete"); ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_DELETE);
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : "")); ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : ""));
sourceIndex++; sourceIndex++;
targetIndex++; targetIndex++;
@ -417,10 +487,10 @@ public class FhirPatch {
} }
private void addInsertItems(IBaseParameters theDiff, List<? extends IBase> theTargetValues, int theTargetIndex, String thePath, BaseRuntimeChildDefinition theChildDefinition) { private void addInsertItems(IBaseParameters theDiff, List<? extends IBase> theTargetValues, int theTargetIndex, String thePath, BaseRuntimeChildDefinition theChildDefinition) {
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation"); IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
ParametersUtil.addPartCode(myContext, operation, "type", "insert"); ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
ParametersUtil.addPartString(myContext, operation, "path", thePath); ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, thePath);
ParametersUtil.addPartInteger(myContext, operation, "index", theTargetIndex); ParametersUtil.addPartInteger(myContext, operation, PARAMETER_INDEX, theTargetIndex);
IBase value = theTargetValues.get(theTargetIndex); IBase value = theTargetValues.get(theTargetIndex);
BaseRuntimeElementDefinition<?> valueDef = myContext.getElementDefinition(value.getClass()); BaseRuntimeElementDefinition<?> valueDef = myContext.getElementDefinition(value.getClass());
@ -432,7 +502,7 @@ public class FhirPatch {
* childen in instead. * childen in instead.
*/ */
if (valueDef.isStandardType()) { if (valueDef.isStandardType()) {
ParametersUtil.addPart(myContext, operation, "value", value); ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, value);
} else { } else {
for (BaseRuntimeChildDefinition nextChild : valueDef.getChildren()) { for (BaseRuntimeChildDefinition nextChild : valueDef.getChildren()) {
List<IBase> childValues = nextChild.getAccessor().getValues(value); List<IBase> childValues = nextChild.getAccessor().getValues(value);
@ -454,7 +524,7 @@ public class FhirPatch {
} }
IBase newValue = massageValueForDiff(theNewValue); IBase newValue = massageValueForDiff(theNewValue);
ParametersUtil.addPart(myContext, theOperationPart, "value", newValue); ParametersUtil.addPart(myContext, theOperationPart, PARAMETER_VALUE, newValue);
} }
private boolean pathIsIgnored(EncodeContextPath theSourceEncodeContext) { private boolean pathIsIgnored(EncodeContextPath theSourceEncodeContext) {
@ -469,19 +539,21 @@ public class FhirPatch {
} }
private IBase massageValueForDiff(IBase theNewValue) { private IBase massageValueForDiff(IBase theNewValue) {
IBase massagedValue = theNewValue;
// XHTML content is dealt with by putting it in a string // XHTML content is dealt with by putting it in a string
if (theNewValue instanceof XhtmlNode) { if (theNewValue instanceof XhtmlNode) {
String xhtmlString = ((XhtmlNode) theNewValue).getValueAsString(); 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 // IIdType can hold a fully qualified ID, but we just want the ID part to show up in diffs
if (theNewValue instanceof IIdType) { if (theNewValue instanceof IIdType) {
String idPart = ((IIdType) theNewValue).getIdPart(); 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) { private String toValue(IPrimitiveType<?> theOldPrimitive) {
@ -490,4 +562,22 @@ public class FhirPatch {
} }
return theOldPrimitive.getValueAsString(); 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;
}
}
} }