Skip to content

Commit 5575e72

Browse files
jdar8Justin_Darjmarchionatto
authored
3788 FHIR patch on soft deleted resource undeletes resource (hapifhir#3789)
* failing test * fix * refactor * refactor * changelog * changelog * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_1_0/3788-fix-fhir-patch-on-soft-deleted-resources.yaml Co-authored-by: jmarchionatto <[email protected]> Co-authored-by: Justin_Dar <[email protected]> Co-authored-by: jmarchionatto <[email protected]>
1 parent 05ebf02 commit 5575e72

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
type: fix
3+
issue: 3788
4+
jira: SMILE-4321
5+
title: "Previously, if a FHIR patch was performed on a soft deleted resource, the patch was successfully applied
6+
and the resource was undeleted and populated with only the data contained in the patch. This has been fixed
7+
so that patch on deleted resources now issues a HTTP 410 response."

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ public DaoMethodOutcome delete(IIdType theId,
550550
}
551551

552552
// Don't delete again if it's already deleted
553-
if (entity.getDeleted() != null) {
553+
if (isDeleted(entity)) {
554554
DaoMethodOutcome outcome = createMethodOutcomeForDelete(entity.getIdDt().getValue());
555555

556556
// used to exist, so we'll set the persistent id
@@ -1145,6 +1145,10 @@ private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchT
11451145

11461146
validateResourceType(entityToUpdate);
11471147

1148+
if (isDeleted(entityToUpdate)) {
1149+
throw createResourceGoneException(entityToUpdate);
1150+
}
1151+
11481152
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
11491153
IBaseResource destination;
11501154
switch (thePatchType) {
@@ -1168,6 +1172,10 @@ private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchT
11681172
return update(destinationCasted, null, true, theRequest);
11691173
}
11701174

1175+
private boolean isDeleted(BaseHasResource entityToUpdate) {
1176+
return entityToUpdate.getDeleted() != null;
1177+
}
1178+
11711179
@PostConstruct
11721180
@Override
11731181
public void start() {
@@ -1208,7 +1216,7 @@ public T readByPid(ResourcePersistentId thePid, boolean theDeletedOk) {
12081216
if (!entity.isPresent()) {
12091217
throw new ResourceNotFoundException(Msg.code(975) + "No resource found with PID " + thePid);
12101218
}
1211-
if (entity.get().getDeleted() != null && !theDeletedOk) {
1219+
if (isDeleted(entity.get()) && !theDeletedOk) {
12121220
throw createResourceGoneException(entity.get());
12131221
}
12141222

@@ -1253,7 +1261,7 @@ public T doRead(IIdType theId, RequestDetails theRequest, boolean theDeletedOk)
12531261
T retVal = toResource(myResourceType, entity, null, false);
12541262

12551263
if (theDeletedOk == false) {
1256-
if (entity.getDeleted() != null) {
1264+
if (isDeleted(entity)) {
12571265
throw createResourceGoneException(entity);
12581266
}
12591267
}
@@ -1808,7 +1816,7 @@ private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean the
18081816
* See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources
18091817
* for a test that needs this.
18101818
*/
1811-
boolean wasDeleted = entity.getDeleted() != null;
1819+
boolean wasDeleted = isDeleted(entity);
18121820
entity.setDeleted(null);
18131821

18141822
/*
@@ -1888,7 +1896,7 @@ private DaoMethodOutcome doUpdateWithHistoryRewrite(T theResource, RequestDetail
18881896
}
18891897
assert resourceId.hasVersionIdPart();
18901898

1891-
boolean wasDeleted = entity.getDeleted() != null;
1899+
boolean wasDeleted = isDeleted(entity);
18921900
entity.setDeleted(null);
18931901
boolean isUpdatingCurrent = resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) == currentEntity.getVersion();
18941902
IBasePersistedResource savedEntity = updateHistoryEntity(theRequest, theResource, currentEntity, entity, resourceId, theTransactionDetails, isUpdatingCurrent);

hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33
import ca.uhn.fhir.i18n.Msg;
44
import ca.uhn.fhir.rest.api.Constants;
55
import ca.uhn.fhir.rest.api.MethodOutcome;
6+
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
7+
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
68
import com.google.common.base.Charsets;
79
import org.apache.commons.io.IOUtils;
810
import org.apache.http.client.methods.CloseableHttpResponse;
911
import org.apache.http.client.methods.HttpPatch;
1012
import org.apache.http.client.methods.HttpPost;
1113
import org.apache.http.entity.ContentType;
1214
import org.apache.http.entity.StringEntity;
15+
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
1316
import org.hl7.fhir.instance.model.api.IIdType;
1417
import org.hl7.fhir.r4.model.Binary;
1518
import org.hl7.fhir.r4.model.BooleanType;
1619
import org.hl7.fhir.r4.model.Bundle;
1720
import org.hl7.fhir.r4.model.CodeType;
21+
import org.hl7.fhir.r4.model.IdType;
1822
import org.hl7.fhir.r4.model.Media;
1923
import org.hl7.fhir.r4.model.Observation;
24+
import org.hl7.fhir.r4.model.OperationOutcome;
2025
import org.hl7.fhir.r4.model.Parameters;
2126
import org.hl7.fhir.r4.model.Patient;
2227
import org.hl7.fhir.r4.model.StringType;
@@ -30,6 +35,9 @@
3035
import static org.hamcrest.CoreMatchers.containsString;
3136
import static org.hamcrest.MatcherAssert.assertThat;
3237
import static org.junit.jupiter.api.Assertions.assertEquals;
38+
import static org.junit.jupiter.api.Assertions.assertFalse;
39+
import static org.junit.jupiter.api.Assertions.assertTrue;
40+
import static org.junit.jupiter.api.Assertions.fail;
3341

3442
public class PatchProviderR4Test extends BaseResourceProviderR4Test {
3543

@@ -143,6 +151,49 @@ public void testFhirPatch_TransactionWithSearchParameter() throws Exception {
143151
assertEquals(false, newPt.getActive());
144152
}
145153

154+
@Test
155+
public void testFhirPatch_AfterDelete_Returns410() {
156+
Patient patient = new Patient();
157+
patient.setActive(true);
158+
patient.addIdentifier().addExtension("http://foo", new StringType("abc"));
159+
patient.addIdentifier().setSystem("sys").setValue("val");
160+
IIdType id = myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
161+
162+
OperationOutcome delOutcome = (OperationOutcome) myClient.delete().resourceById(id).execute().getOperationOutcome();
163+
assertTrue(delOutcome.getIssue().get(0).getDiagnostics().contains("Successfully deleted"));
164+
165+
Parameters patch = new Parameters();
166+
Parameters.ParametersParameterComponent operation = patch.addParameter();
167+
operation.setName("operation");
168+
operation
169+
.addPart()
170+
.setName("type")
171+
.setValue(new CodeType("replace"));
172+
operation
173+
.addPart()
174+
.setName("path")
175+
.setValue(new StringType("Patient.active"));
176+
operation
177+
.addPart()
178+
.setName("value")
179+
.setValue(new BooleanType(false));
180+
181+
182+
try {
183+
myClient.patch().withFhirPatch(patch).withId(id).execute();
184+
fail();
185+
} catch (ResourceGoneException e) {
186+
assertEquals(Constants.STATUS_HTTP_410_GONE, e.getStatusCode());
187+
}
188+
189+
try {
190+
myClient.read().resource(Patient.class).withId(id).execute();
191+
fail();
192+
} catch (ResourceGoneException e) {
193+
assertEquals(Constants.STATUS_HTTP_410_GONE, e.getStatusCode());
194+
}
195+
}
196+
146197
@Test
147198
public void testPatchAddArray() throws IOException {
148199
IIdType id;

0 commit comments

Comments
 (0)