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:
parent
9d34e6b248
commit
0475cb682f
|
@ -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<>();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue