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);
|
||||
}
|
||||
|
||||
public static Optional<Integer> getParameterPartValueAsInteger(FhirContext theCtx, IBase theParameter, String theParameterName) {
|
||||
return getParameterPartValue(theCtx, theParameter, theParameterName)
|
||||
.filter(t -> IPrimitiveType.class.isAssignableFrom(t.getClass()))
|
||||
.map(t -> (IPrimitiveType<?>) t)
|
||||
.map(IPrimitiveType::getValue)
|
||||
.filter(t -> Integer.class.isAssignableFrom(t.getClass()))
|
||||
.map(t -> (Integer) t);
|
||||
}
|
||||
|
||||
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
|
||||
List<T> retVal = new ArrayList<>();
|
||||
|
||||
|
|
|
@ -45,6 +45,16 @@
|
|||
</exclusions>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.json</groupId>
|
||||
<artifactId>javax.json-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.patch;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -21,8 +23,11 @@ import org.hl7.fhir.r4.model.StringType;
|
|||
import org.hl7.fhir.r4.model.Type;
|
||||
import org.hl7.fhir.r4.model.UriType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.util.XmlExpectationsHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -33,6 +38,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FhirPatchApplyR4Test {
|
||||
|
||||
|
@ -89,6 +95,35 @@ public class FhirPatchApplyR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertToInvalidIndex_minimum() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
|
||||
Parameters patch = new Parameters();
|
||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
||||
operation.setName("operation");
|
||||
operation
|
||||
.addPart()
|
||||
.setName("type")
|
||||
.setValue(new CodeType("insert"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("path")
|
||||
.setValue(new StringType("Patient.identifier"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("index")
|
||||
.setValue(new IntegerType(-1));
|
||||
|
||||
try {
|
||||
svc.apply(patient, patch);
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(Msg.code(1270) + "Invalid insert index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveFromInvalidIndex() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
@ -122,6 +157,39 @@ public class FhirPatchApplyR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveFromInvalidIndex_minimum() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
|
||||
Parameters patch = new Parameters();
|
||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
||||
operation.setName("operation");
|
||||
operation
|
||||
.addPart()
|
||||
.setName("type")
|
||||
.setValue(new CodeType("move"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("path")
|
||||
.setValue(new StringType("Patient.identifier"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("source")
|
||||
.setValue(new IntegerType(-1));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("destination")
|
||||
.setValue(new IntegerType(1));
|
||||
|
||||
try {
|
||||
svc.apply(patient, patch);
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(Msg.code(1268) + "Invalid move source index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveToInvalidIndex() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
@ -156,6 +224,40 @@ public class FhirPatchApplyR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveToInvalidIndex_minimum() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("sys");
|
||||
|
||||
Parameters patch = new Parameters();
|
||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
||||
operation.setName("operation");
|
||||
operation
|
||||
.addPart()
|
||||
.setName("type")
|
||||
.setValue(new CodeType("move"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("path")
|
||||
.setValue(new StringType("Patient.identifier"));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("source")
|
||||
.setValue(new IntegerType(0));
|
||||
operation
|
||||
.addPart()
|
||||
.setName("destination")
|
||||
.setValue(new IntegerType(-1));
|
||||
|
||||
try {
|
||||
svc.apply(patient, patch);
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(Msg.code(1269) + "Invalid move destination index -1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteItemWithExtension() {
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
|
@ -496,4 +598,49 @@ public class FhirPatchApplyR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
// This implements the official HL7 test suite, as defined in http://hl7.org/fhir/R4/test-cases.zip. There may be some overlap with the cases above.
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock = """
|
||||
No Difference, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>
|
||||
Replace Primitive, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part><part><name value="value"/><valueDate value="1930-01-01"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1930-01-01"/></Patient>
|
||||
Delete Primitive, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
|
||||
Add Primitive, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="birthDate"/></part><part><name value="value"/><valueDate value="1930-01-01"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1930-01-01"/></Patient>
|
||||
Delete Primitive #2, <Patient xmlns="http://hl7.org/fhir"><birthDate value="1920-01-01"/></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.birthDate"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
|
||||
Replace Nested Primitive #1, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.contact[0].gender"/></part><part><name value="value"/><valueCode value="female"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="female"/></contact></Patient>
|
||||
Replace Nested Primitive #2, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.contact[0].name.text"/></part><part><name value="value"/><valueString value="the name"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="the name"/></name><gender value="male"/></contact></Patient>
|
||||
Delete Nested Primitive #1, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0].gender"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>
|
||||
Delete Nested Primitive #2, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0].name.text"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><gender value="male"/></contact></Patient>
|
||||
Add Nested Primitive, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient.contact[0]"/></part><part><name value="name"/><valueString value="gender"/></part><part><name value="value"/><valueCode value="male"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name><gender value="male"/></contact></Patient>
|
||||
Add Complex, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="maritalStatus"/></part><part><name value="value"/><valueCodeableConcept><text value="married"/></valueCodeableConcept></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><maritalStatus><text value="married"/></maritalStatus></Patient>
|
||||
Replace Complex, <Patient xmlns="http://hl7.org/fhir"><maritalStatus id="1"><text value="married"/></maritalStatus></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.maritalStatus"/></part><part><name value="value"/><valueCodeableConcept id="2"><text value="not married"/></valueCodeableConcept></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><maritalStatus id="2"><text value="not married"/></maritalStatus></Patient>
|
||||
Delete Complex, <Patient xmlns="http://hl7.org/fhir"><maritalStatus><text value="married"/></maritalStatus></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.maritalStatus"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
|
||||
Add Anonymous Type, <Patient xmlns="http://hl7.org/fhir"></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="add"/></part><part><name value="path"/><valueString value="Patient"/></part><part><name value="name"/><valueString value="contact"/></part><part><name value="value"/><part><name value="name"/><valueHumanName><text value="a name"/></valueHumanName></part></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>
|
||||
Delete Anonymous Type, <Patient xmlns="http://hl7.org/fhir"><contact><name><text value="a name"/></name></contact></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.contact[0]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"></Patient>
|
||||
List unchanged, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
|
||||
List unchanged contents changed, <Patient xmlns="http://hl7.org/fhir"><identifier id="a"><system value="http://example.org"/><value value="value 1"/></identifier><identifier id="b"><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.identifier[0].value"/></part><part><name value="value"/><valueString value="value 2"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="replace"/></part><part><name value="path"/><valueString value="Patient.identifier[1].value"/></part><part><name value="value"/><valueString value="value 1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier id="a"><system value="http://example.org"/><value value="value 2"/></identifier><identifier id="b"><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
|
||||
Add to list, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="2"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Insert in list #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="1"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
|
||||
Insert in list #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="insert"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="index"/><valueInteger value="0"/></part><part><name value="value"/><valueIdentifier><system value="http://example.org"/><value value="value 3"/></valueIdentifier></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
|
||||
Delete from List #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[0]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Delete from List #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[1]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Delete from List #3, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="delete"/></part><part><name value="path"/><valueString value="Patient.identifier[2]"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier></Patient>
|
||||
Reorder List #1, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Reorder List #2, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Reorder List #3, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="2"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier></Patient>
|
||||
Reorder List #4, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="0"/></part><part><name value="destination"/><valueInteger value="3"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
|
||||
Reorder List #5, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="1"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="2"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>
|
||||
Reorder List #6, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 1"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 4"/></identifier></Patient>, <Parameters xmlns="http://hl7.org/fhir"><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="0"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="1"/></part></parameter><parameter><name value="operation"/><part><name value="type"/><valueCode value="move"/></part><part><name value="path"/><valueString value="Patient.identifier"/></part><part><name value="source"/><valueInteger value="3"/></part><part><name value="destination"/><valueInteger value="2"/></part></parameter></Parameters>, <Patient xmlns="http://hl7.org/fhir"><identifier><system value="http://example.org"/><value value="value 4"/></identifier><identifier><system value="http://example.org"/><value value="value 3"/></identifier><identifier><system value="http://example.org"/><value value="value 2"/></identifier><identifier><system value="http://example.org"/><value value="value 1"/></identifier></Patient>
|
||||
""")
|
||||
public void testHL7Cases(String theName, String theInputResource, String thePatch, String theOutputResource) throws Exception {
|
||||
IParser parser = ourCtx.newXmlParser();
|
||||
Patient patient = parser.parseResource(Patient.class, theInputResource);
|
||||
Parameters parameters = parser.parseResource(Parameters.class, thePatch);
|
||||
Patient expectedPatient = parser.parseResource(Patient.class, theOutputResource);
|
||||
|
||||
FhirPatch svc = new FhirPatch(ourCtx);
|
||||
svc.apply(patient, parameters);
|
||||
|
||||
new XmlExpectationsHelper().assertXmlEqual(theOutputResource, parser.encodeResourceToString(patient));
|
||||
assertTrue(expectedPatient.equalsDeep(patient), theName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,20 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
public class FhirPatch {
|
||||
|
||||
public static final String OPERATION_ADD = "add";
|
||||
public static final String OPERATION_DELETE = "delete";
|
||||
public static final String OPERATION_INSERT = "insert";
|
||||
public static final String OPERATION_MOVE = "move";
|
||||
public static final String OPERATION_REPLACE = "replace";
|
||||
public static final String PARAMETER_DESTINATION = "destination";
|
||||
public static final String PARAMETER_INDEX = "index";
|
||||
public static final String PARAMETER_NAME = "name";
|
||||
public static final String PARAMETER_OPERATION = "operation";
|
||||
public static final String PARAMETER_PATH = "path";
|
||||
public static final String PARAMETER_SOURCE = "source";
|
||||
public static final String PARAMETER_TYPE = "type";
|
||||
public static final String PARAMETER_VALUE = "value";
|
||||
|
||||
private final FhirContext myContext;
|
||||
private boolean myIncludePreviousValueInDiff;
|
||||
private Set<EncodeContextPath> myIgnorePaths = Collections.emptySet();
|
||||
|
@ -81,232 +95,288 @@ public class FhirPatch {
|
|||
|
||||
public void apply(IBaseResource theResource, IBaseResource thePatch) {
|
||||
|
||||
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, "operation");
|
||||
for (IBase nextOp : opParameters) {
|
||||
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "type");
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "path");
|
||||
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, nextOp, "value");
|
||||
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, nextOp, "value");
|
||||
|
||||
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, PARAMETER_OPERATION);
|
||||
for (IBase nextOperation : opParameters) {
|
||||
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOperation, PARAMETER_TYPE);
|
||||
type = defaultString(type);
|
||||
path = defaultString(path);
|
||||
|
||||
String containingPath;
|
||||
String elementName;
|
||||
Integer removeIndex = null;
|
||||
Integer insertIndex = null;
|
||||
if ("delete".equals(type)) {
|
||||
if (path.endsWith(")")) {
|
||||
// This is probably a filter, so we're probably dealing with a list
|
||||
int filterArgsIndex = path.lastIndexOf('('); // Let's hope there aren't nested parentheses
|
||||
int lastDotIndex = path.lastIndexOf('.', filterArgsIndex); // There might be a dot inside the parentheses, so look to the left of that
|
||||
int secondLastDotIndex = path.lastIndexOf('.', lastDotIndex-1);
|
||||
containingPath = path.substring(0, secondLastDotIndex);
|
||||
elementName = path.substring(secondLastDotIndex + 1, lastDotIndex);
|
||||
} else if (path.endsWith("]")) {
|
||||
// This is almost definitely a list
|
||||
int openBracketIndex = path.lastIndexOf('[');
|
||||
int lastDotIndex = path.lastIndexOf('.', openBracketIndex);
|
||||
containingPath = path.substring(0, lastDotIndex);
|
||||
elementName = path.substring(lastDotIndex + 1, openBracketIndex);
|
||||
} else {
|
||||
doDelete(theResource, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if ("add".equals(type)) {
|
||||
|
||||
containingPath = path;
|
||||
elementName = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "name");
|
||||
|
||||
} else if ("replace".equals(type)) {
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
containingPath = path.substring(0, lastDot);
|
||||
elementName = path.substring(lastDot + 1);
|
||||
|
||||
} else if ("insert".equals(type)) {
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
containingPath = path.substring(0, lastDot);
|
||||
elementName = path.substring(lastDot + 1);
|
||||
insertIndex = ParametersUtil
|
||||
.getParameterPartValue(myContext, nextOp, "index")
|
||||
.map(t -> (IPrimitiveType<Integer>) t)
|
||||
.map(t -> t.getValue())
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
||||
|
||||
} else if ("move".equals(type)) {
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
containingPath = path.substring(0, lastDot);
|
||||
elementName = path.substring(lastDot + 1);
|
||||
insertIndex = ParametersUtil
|
||||
.getParameterPartValue(myContext, nextOp, "destination")
|
||||
.map(t -> (IPrimitiveType<Integer>) t)
|
||||
.map(t -> t.getValue())
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
||||
removeIndex = ParametersUtil
|
||||
.getParameterPartValue(myContext, nextOp, "source")
|
||||
.map(t -> (IPrimitiveType<Integer>) t)
|
||||
.map(t -> t.getValue())
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
||||
|
||||
if (OPERATION_DELETE.equals(type)) {
|
||||
handleDeleteOperation(theResource, nextOperation);
|
||||
} else if (OPERATION_ADD.equals(type)) {
|
||||
handleAddOperation(theResource, nextOperation);
|
||||
} else if (OPERATION_REPLACE.equals(type)) {
|
||||
handleReplaceOperation(theResource, nextOperation);
|
||||
} else if (OPERATION_INSERT.equals(type)) {
|
||||
handleInsertOperation(theResource, nextOperation);
|
||||
} else if (OPERATION_MOVE.equals(type)) {
|
||||
handleMoveOperation(theResource, nextOperation);
|
||||
} else {
|
||||
|
||||
throw new InvalidRequestException(Msg.code(1267) + "Unknown patch operation type: " + type);
|
||||
|
||||
}
|
||||
|
||||
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase next : paths) {
|
||||
|
||||
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(next.getClass());
|
||||
|
||||
String childName = elementName;
|
||||
BaseRuntimeChildDefinition childDef = elementDef.getChildByName(childName);
|
||||
BaseRuntimeElementDefinition<?> childElement;
|
||||
if (childDef == null) {
|
||||
childName = elementName + "[x]";
|
||||
childDef = elementDef.getChildByName(childName);
|
||||
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
|
||||
} else {
|
||||
childElement = childDef.getChildByName(childName);
|
||||
}
|
||||
|
||||
if ("move".equals(type)) {
|
||||
|
||||
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
|
||||
if (removeIndex == null || removeIndex >= existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveSourceIndex", removeIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1268) + msg);
|
||||
}
|
||||
IBase newValue = existingValues.remove(removeIndex.intValue());
|
||||
|
||||
if (insertIndex == null || insertIndex > existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveDestinationIndex", insertIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1269) + msg);
|
||||
}
|
||||
existingValues.add(insertIndex, newValue);
|
||||
|
||||
childDef.getMutator().setValue(next, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDef.getMutator().addValue(next, nextNewValue);
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if ("delete".equals(type)) {
|
||||
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
|
||||
List<IBase> elementsToRemove = myContext.newFhirPath().evaluate(theResource, path, IBase.class);
|
||||
existingValues.removeAll(elementsToRemove);
|
||||
|
||||
childDef.getMutator().setValue(next, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDef.getMutator().addValue(next, nextNewValue);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
IBase newValue;
|
||||
if (valuePartValue.isPresent()) {
|
||||
newValue = valuePartValue.get();
|
||||
} else {
|
||||
newValue = childElement.newInstance();
|
||||
|
||||
if (valuePart.isPresent()) {
|
||||
List<IBase> valuePartParts = myContext.newTerser().getValues(valuePart.get(), "part");
|
||||
for (IBase nextValuePartPart : valuePartParts) {
|
||||
|
||||
String name = myContext.newTerser().getSingleValue(nextValuePartPart, "name", IPrimitiveType.class).map(t -> t.getValueAsString()).orElse(null);
|
||||
if (isNotBlank(name)) {
|
||||
|
||||
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
|
||||
if (value.isPresent()) {
|
||||
|
||||
BaseRuntimeChildDefinition partChildDef = childElement.getChildByName(name);
|
||||
if (partChildDef == null) {
|
||||
name = name + "[x]";
|
||||
partChildDef = childElement.getChildByName(name);
|
||||
}
|
||||
partChildDef.getMutator().addValue(newValue, value.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (IBaseEnumeration.class.isAssignableFrom(childElement.getImplementingClass()) || XhtmlNode.class.isAssignableFrom(childElement.getImplementingClass())) {
|
||||
// If the compositeElementDef is an IBaseEnumeration, we will use the actual compositeElementDef definition to build one, since
|
||||
// it needs the right factory object passed to its constructor
|
||||
IPrimitiveType<?> newValueInstance = (IPrimitiveType<?>) childElement.newInstance();
|
||||
newValueInstance.setValueAsString(((IPrimitiveType<?>) newValue).getValueAsString());
|
||||
childDef.getMutator().setValue(next, newValueInstance);
|
||||
newValue = newValueInstance;
|
||||
}
|
||||
|
||||
if ("insert".equals(type)) {
|
||||
|
||||
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
|
||||
if (insertIndex == null || insertIndex > existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidInsertIndex", insertIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1270) + msg);
|
||||
}
|
||||
existingValues.add(insertIndex, newValue);
|
||||
|
||||
childDef.getMutator().setValue(next, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDef.getMutator().addValue(next, nextNewValue);
|
||||
}
|
||||
|
||||
} else if ("add".equals(type)) {
|
||||
childDef.getMutator().addValue(next, newValue);
|
||||
} else if ("replace".equals(type)) {
|
||||
childDef.getMutator().setValue(next, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void doDelete(IBaseResource theResource, String thePath) {
|
||||
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, thePath, IBase.class);
|
||||
for (IBase next : paths) {
|
||||
myContext.newTerser().visit(next, new IModelVisitor2() {
|
||||
@Override
|
||||
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
if (theElement instanceof IPrimitiveType) {
|
||||
((IPrimitiveType<?>) theElement).setValueAsString(null);
|
||||
private void handleAddOperation(IBaseResource theResource, IBase theParameters) {
|
||||
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
|
||||
String elementName = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_NAME);
|
||||
|
||||
String containingPath = defaultString(path);
|
||||
|
||||
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase nextElement : containingElements) {
|
||||
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
|
||||
|
||||
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
|
||||
|
||||
childDefinition.getChildDef().getMutator().addValue(nextElement, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInsertOperation(IBaseResource theResource, IBase theParameters) {
|
||||
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
|
||||
path = defaultString(path);
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
String containingPath = path.substring(0, lastDot);
|
||||
String elementName = path.substring(lastDot + 1);
|
||||
Integer insertIndex = ParametersUtil
|
||||
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_INDEX)
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
||||
|
||||
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase nextElement : containingElements) {
|
||||
|
||||
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
|
||||
|
||||
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
|
||||
|
||||
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(nextElement));
|
||||
if (insertIndex == null || insertIndex < 0 || insertIndex > existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidInsertIndex", insertIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1270) + msg);
|
||||
}
|
||||
existingValues.add(insertIndex, newValue);
|
||||
|
||||
childDefinition.getChildDef().getMutator().setValue(nextElement, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDefinition.getChildDef().getMutator().addValue(nextElement, nextNewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeleteOperation(IBaseResource theResource, IBase theParameters) {
|
||||
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
|
||||
path = defaultString(path);
|
||||
|
||||
String containingPath;
|
||||
String elementName;
|
||||
|
||||
if (path.endsWith(")")) {
|
||||
// This is probably a filter, so we're probably dealing with a list
|
||||
int filterArgsIndex = path.lastIndexOf('('); // Let's hope there aren't nested parentheses
|
||||
int lastDotIndex = path.lastIndexOf('.', filterArgsIndex); // There might be a dot inside the parentheses, so look to the left of that
|
||||
int secondLastDotIndex = path.lastIndexOf('.', lastDotIndex-1);
|
||||
containingPath = path.substring(0, secondLastDotIndex);
|
||||
elementName = path.substring(secondLastDotIndex + 1, lastDotIndex);
|
||||
} else if (path.endsWith("]")) {
|
||||
// This is almost definitely a list
|
||||
int openBracketIndex = path.lastIndexOf('[');
|
||||
int lastDotIndex = path.lastIndexOf('.', openBracketIndex);
|
||||
containingPath = path.substring(0, lastDotIndex);
|
||||
elementName = path.substring(lastDotIndex + 1, openBracketIndex);
|
||||
} else {
|
||||
containingPath = path;
|
||||
elementName = null;
|
||||
}
|
||||
|
||||
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase nextElement : containingElements) {
|
||||
if (elementName == null) {
|
||||
deleteSingleElement(nextElement);
|
||||
} else {
|
||||
deleteFromList(theResource, nextElement, elementName, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteFromList(IBaseResource theResource, IBase theContainingElement, String theListElementName, String theElementToDeletePath) {
|
||||
ChildDefinition childDefinition = findChildDefinition(theContainingElement, theListElementName);
|
||||
|
||||
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(theContainingElement));
|
||||
List<IBase> elementsToRemove = myContext.newFhirPath().evaluate(theResource, theElementToDeletePath, IBase.class);
|
||||
existingValues.removeAll(elementsToRemove);
|
||||
|
||||
childDefinition.getChildDef().getMutator().setValue(theContainingElement, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDefinition.getChildDef().getMutator().addValue(theContainingElement, nextNewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReplaceOperation(IBaseResource theResource, IBase theParameters) {
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
|
||||
path = defaultString(path);
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
String containingPath = path.substring(0, lastDot);
|
||||
String elementName = path.substring(lastDot + 1);
|
||||
|
||||
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase nextElement : containingElements) {
|
||||
|
||||
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
|
||||
|
||||
IBase newValue = getNewValue(theParameters, nextElement, childDefinition);
|
||||
|
||||
childDefinition.getChildDef().getMutator().setValue(nextElement, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMoveOperation(IBaseResource theResource, IBase theParameters) {
|
||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, theParameters, PARAMETER_PATH);
|
||||
path = defaultString(path);
|
||||
|
||||
int lastDot = path.lastIndexOf(".");
|
||||
String containingPath = path.substring(0, lastDot);
|
||||
String elementName = path.substring(lastDot + 1);
|
||||
Integer insertIndex = ParametersUtil
|
||||
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_DESTINATION)
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for move operation"));
|
||||
Integer removeIndex = ParametersUtil
|
||||
.getParameterPartValueAsInteger(myContext, theParameters, PARAMETER_SOURCE)
|
||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for move operation"));
|
||||
|
||||
List<IBase> containingElements = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
||||
for (IBase nextElement : containingElements) {
|
||||
|
||||
ChildDefinition childDefinition = findChildDefinition(nextElement, elementName);
|
||||
|
||||
List<IBase> existingValues = new ArrayList<>(childDefinition.getChildDef().getAccessor().getValues(nextElement));
|
||||
if (removeIndex == null || removeIndex < 0 || removeIndex >= existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveSourceIndex", removeIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1268) + msg);
|
||||
}
|
||||
IBase newValue = existingValues.remove(removeIndex.intValue());
|
||||
|
||||
if (insertIndex == null || insertIndex < 0 || insertIndex > existingValues.size()) {
|
||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveDestinationIndex", insertIndex, path, existingValues.size());
|
||||
throw new InvalidRequestException(Msg.code(1269) + msg);
|
||||
}
|
||||
existingValues.add(insertIndex, newValue);
|
||||
|
||||
childDefinition.getChildDef().getMutator().setValue(nextElement, null);
|
||||
for (IBase nextNewValue : existingValues) {
|
||||
childDefinition.getChildDef().getMutator().addValue(nextElement, nextNewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ChildDefinition findChildDefinition(IBase theContainingElement, String theElementName) {
|
||||
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(theContainingElement.getClass());
|
||||
|
||||
String childName = theElementName;
|
||||
BaseRuntimeChildDefinition childDef = elementDef.getChildByName(childName);
|
||||
BaseRuntimeElementDefinition<?> childElement;
|
||||
if (childDef == null) {
|
||||
childName = theElementName + "[x]";
|
||||
childDef = elementDef.getChildByName(childName);
|
||||
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
|
||||
} else {
|
||||
childElement = childDef.getChildByName(childName);
|
||||
}
|
||||
|
||||
return new ChildDefinition(childDef, childElement);
|
||||
}
|
||||
|
||||
private IBase getNewValue(IBase theParameters, IBase theElement, ChildDefinition theChildDefinition) {
|
||||
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, theParameters, PARAMETER_VALUE);
|
||||
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, theParameters, PARAMETER_VALUE);
|
||||
|
||||
IBase newValue;
|
||||
if (valuePartValue.isPresent()) {
|
||||
newValue = valuePartValue.get();
|
||||
} else {
|
||||
newValue = theChildDefinition.getChildElement().newInstance();
|
||||
|
||||
if (valuePart.isPresent()) {
|
||||
IBase theValueElement = valuePart.get();
|
||||
populateNewValue(theChildDefinition, newValue, theValueElement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (IBaseEnumeration.class.isAssignableFrom(theChildDefinition.getChildElement().getImplementingClass()) || XhtmlNode.class.isAssignableFrom(theChildDefinition.getChildElement().getImplementingClass())) {
|
||||
// If the compositeElementDef is an IBaseEnumeration, we will use the actual compositeElementDef definition to build one, since
|
||||
// it needs the right factory object passed to its constructor
|
||||
IPrimitiveType<?> newValueInstance;
|
||||
if (theChildDefinition.getChildDef().getInstanceConstructorArguments() != null) {
|
||||
newValueInstance = (IPrimitiveType<?>) theChildDefinition.getChildElement().newInstance(
|
||||
theChildDefinition.getChildDef().getInstanceConstructorArguments());
|
||||
} else {
|
||||
newValueInstance = (IPrimitiveType<?>) theChildDefinition.getChildElement().newInstance();
|
||||
}
|
||||
newValueInstance.setValueAsString(((IPrimitiveType<?>) newValue).getValueAsString());
|
||||
theChildDefinition.getChildDef().getMutator().setValue(theElement, newValueInstance);
|
||||
newValue = newValueInstance;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
|
||||
private void populateNewValue(ChildDefinition theChildDefinition, IBase theNewValue, IBase theValueElement) {
|
||||
List<IBase> valuePartParts = myContext.newTerser().getValues(theValueElement, "part");
|
||||
for (IBase nextValuePartPart : valuePartParts) {
|
||||
|
||||
String name = myContext.newTerser().getSingleValue(nextValuePartPart, PARAMETER_NAME, IPrimitiveType.class).map(IPrimitiveType::getValueAsString).orElse(null);
|
||||
if (isNotBlank(name)) {
|
||||
|
||||
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
|
||||
if (value.isPresent()) {
|
||||
|
||||
BaseRuntimeChildDefinition partChildDef = theChildDefinition.getChildElement().getChildByName(name);
|
||||
if (partChildDef == null) {
|
||||
name = name + "[x]";
|
||||
partChildDef = theChildDefinition.getChildElement().getChildByName(name);
|
||||
}
|
||||
return true;
|
||||
partChildDef.getMutator().addValue(theNewValue, value.get());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
theNextExt.setUrl(null);
|
||||
theNextExt.setValue(null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteSingleElement(IBase theElementToDelete) {
|
||||
myContext.newTerser().visit(theElementToDelete, new IModelVisitor2() {
|
||||
@Override
|
||||
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
if (theElement instanceof IPrimitiveType) {
|
||||
((IPrimitiveType<?>) theElement).setValueAsString(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
theNextExt.setUrl(null);
|
||||
theNextExt.setValue(null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public IBaseParameters diff(@Nullable IBaseResource theOldValue, @Nonnull IBaseResource theNewValue) {
|
||||
IBaseParameters retVal = ParametersUtil.newInstance(myContext);
|
||||
String newValueTypeName = myContext.getResourceDefinition(theNewValue).getName();
|
||||
|
||||
if (theOldValue == null) {
|
||||
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, "operation");
|
||||
ParametersUtil.addPartCode(myContext, operation, "type", "insert");
|
||||
ParametersUtil.addPartString(myContext, operation, "path", newValueTypeName);
|
||||
ParametersUtil.addPart(myContext, operation, "value", theNewValue);
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, retVal, PARAMETER_OPERATION);
|
||||
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
|
||||
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, newValueTypeName);
|
||||
ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, theNewValue);
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -339,9 +409,9 @@ public class FhirPatch {
|
|||
BaseRuntimeElementDefinition<?> sourceDef = myContext.getElementDefinition(theOldField.getClass());
|
||||
BaseRuntimeElementDefinition<?> targetDef = myContext.getElementDefinition(theNewField.getClass());
|
||||
if (!sourceDef.getName().equals(targetDef.getName())) {
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
||||
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
|
||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
|
||||
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
|
||||
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
|
||||
addValueToDiff(operation, theOldField, theNewField);
|
||||
} else {
|
||||
if (theOldField instanceof IPrimitiveType) {
|
||||
|
@ -350,9 +420,9 @@ public class FhirPatch {
|
|||
String oldValueAsString = toValue(oldPrimitive);
|
||||
String newValueAsString = toValue(newPrimitive);
|
||||
if (!Objects.equals(oldValueAsString, newValueAsString)) {
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
||||
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
|
||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
|
||||
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_REPLACE);
|
||||
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath);
|
||||
addValueToDiff(operation, oldPrimitive, newPrimitive);
|
||||
}
|
||||
}
|
||||
|
@ -405,9 +475,9 @@ public class FhirPatch {
|
|||
|
||||
// Find deleted items
|
||||
while (sourceIndex < sourceValues.size()) {
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
||||
ParametersUtil.addPartCode(myContext, operation, "type", "delete");
|
||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : ""));
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
|
||||
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_DELETE);
|
||||
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, theTargetPath + "." + elementName + (repeatable ? "[" + targetIndex + "]" : ""));
|
||||
|
||||
sourceIndex++;
|
||||
targetIndex++;
|
||||
|
@ -417,10 +487,10 @@ public class FhirPatch {
|
|||
}
|
||||
|
||||
private void addInsertItems(IBaseParameters theDiff, List<? extends IBase> theTargetValues, int theTargetIndex, String thePath, BaseRuntimeChildDefinition theChildDefinition) {
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
||||
ParametersUtil.addPartCode(myContext, operation, "type", "insert");
|
||||
ParametersUtil.addPartString(myContext, operation, "path", thePath);
|
||||
ParametersUtil.addPartInteger(myContext, operation, "index", theTargetIndex);
|
||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, PARAMETER_OPERATION);
|
||||
ParametersUtil.addPartCode(myContext, operation, PARAMETER_TYPE, OPERATION_INSERT);
|
||||
ParametersUtil.addPartString(myContext, operation, PARAMETER_PATH, thePath);
|
||||
ParametersUtil.addPartInteger(myContext, operation, PARAMETER_INDEX, theTargetIndex);
|
||||
|
||||
IBase value = theTargetValues.get(theTargetIndex);
|
||||
BaseRuntimeElementDefinition<?> valueDef = myContext.getElementDefinition(value.getClass());
|
||||
|
@ -432,7 +502,7 @@ public class FhirPatch {
|
|||
* childen in instead.
|
||||
*/
|
||||
if (valueDef.isStandardType()) {
|
||||
ParametersUtil.addPart(myContext, operation, "value", value);
|
||||
ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, value);
|
||||
} else {
|
||||
for (BaseRuntimeChildDefinition nextChild : valueDef.getChildren()) {
|
||||
List<IBase> childValues = nextChild.getAccessor().getValues(value);
|
||||
|
@ -454,7 +524,7 @@ public class FhirPatch {
|
|||
}
|
||||
|
||||
IBase newValue = massageValueForDiff(theNewValue);
|
||||
ParametersUtil.addPart(myContext, theOperationPart, "value", newValue);
|
||||
ParametersUtil.addPart(myContext, theOperationPart, PARAMETER_VALUE, newValue);
|
||||
}
|
||||
|
||||
private boolean pathIsIgnored(EncodeContextPath theSourceEncodeContext) {
|
||||
|
@ -469,19 +539,21 @@ public class FhirPatch {
|
|||
}
|
||||
|
||||
private IBase massageValueForDiff(IBase theNewValue) {
|
||||
IBase massagedValue = theNewValue;
|
||||
|
||||
// XHTML content is dealt with by putting it in a string
|
||||
if (theNewValue instanceof XhtmlNode) {
|
||||
String xhtmlString = ((XhtmlNode) theNewValue).getValueAsString();
|
||||
theNewValue = myContext.getElementDefinition("string").newInstance(xhtmlString);
|
||||
massagedValue = myContext.getElementDefinition("string").newInstance(xhtmlString);
|
||||
}
|
||||
|
||||
// IIdType can hold a fully qualified ID, but we just want the ID part to show up in diffs
|
||||
if (theNewValue instanceof IIdType) {
|
||||
String idPart = ((IIdType) theNewValue).getIdPart();
|
||||
theNewValue = myContext.getElementDefinition("id").newInstance(idPart);
|
||||
massagedValue = myContext.getElementDefinition("id").newInstance(idPart);
|
||||
}
|
||||
|
||||
return theNewValue;
|
||||
return massagedValue;
|
||||
}
|
||||
|
||||
private String toValue(IPrimitiveType<?> theOldPrimitive) {
|
||||
|
@ -490,4 +562,22 @@ public class FhirPatch {
|
|||
}
|
||||
return theOldPrimitive.getValueAsString();
|
||||
}
|
||||
|
||||
private static class ChildDefinition {
|
||||
private final BaseRuntimeChildDefinition myChildDef;
|
||||
private final BaseRuntimeElementDefinition<?> myChildElement;
|
||||
|
||||
public ChildDefinition(BaseRuntimeChildDefinition theChildDef, BaseRuntimeElementDefinition<?> theChildElement) {
|
||||
this.myChildDef = theChildDef;
|
||||
this.myChildElement = theChildElement;
|
||||
}
|
||||
|
||||
public BaseRuntimeChildDefinition getChildDef() {
|
||||
return myChildDef;
|
||||
}
|
||||
|
||||
public BaseRuntimeElementDefinition<?> getChildElement() {
|
||||
return myChildElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue