Skip to content

Commit

Permalink
allow deleting resources if references are all versioned only
Browse files Browse the repository at this point in the history
  • Loading branch information
leif stawnyczy committed Jan 23, 2025
1 parent 3793d81 commit 4c0a71f
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ public class DeleteConflictFinderService {
protected EntityManager myEntityManager;

List<ResourceLink> findConflicts(ResourceTable theEntity, int maxResults) {
TypedQuery<ResourceLink> query = myEntityManager.createQuery(
"SELECT l FROM ResourceLink l WHERE l.myTargetResource.myPid = :target_pid", ResourceLink.class);
String queryStr = "SELECT l FROM ResourceLink l WHERE l.myTargetResource.myPid = :target_pid AND (l.myTargetResourceVersion IS NULL)";
TypedQuery<ResourceLink> query = myEntityManager.createQuery(queryStr, ResourceLink.class);
query.setParameter("target_pid", theEntity.getId());
query.setMaxResults(maxResults);
return query.getResultList();
List<ResourceLink> resourceLinks = query.getResultList();

return resourceLinks;
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
package ca.uhn.fhir.jpa.delete;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Function;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;


public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictServiceR4Test.class);


private final DeleteConflictInterceptor myDeleteInterceptor = new DeleteConflictInterceptor();
private int myInterceptorDeleteCount;

Expand Down Expand Up @@ -204,6 +213,139 @@ public void testBadInterceptorNoInfiniteLoop() {
assertEquals(1 + DeleteConflictService.MAX_RETRY_ATTEMPTS, myDeleteInterceptor.myCallCount);
}

private void setupResourceReferenceTests() {
// we don't need this
myInterceptorRegistry.unregisterInterceptor(myDeleteInterceptor);

// we have to allow versioned references; by default we do not
myFhirContext.getParserOptions()
.setStripVersionsFromReferences(false);
}

@ParameterizedTest
@ValueSource(booleans = { true, false })
public void delete_resourceReferencedByVersionedReferenceOnly_succeeds(boolean theUpdateObs) {
// setup
SystemRequestDetails requestDetails = new SystemRequestDetails();
DaoMethodOutcome outcome;

setupResourceReferenceTests();

Observation observation = new Observation();
observation.setStatus(Observation.ObservationStatus.FINAL);
outcome = myObservationDao.create(observation, requestDetails);
IIdType obsId = outcome.getId();

// create encounter that references Observation by versioned reference only
Encounter encounter = new Encounter();
encounter.setStatus(Encounter.EncounterStatus.FINISHED);
Coding coding = new Coding();
coding.setSystem("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode");
coding.setCode("AMB");
encounter.setClass_(coding);
encounter.addReasonReference()
.setReference(obsId.getValue()); // versioned reference
outcome = myEncounterDao.create(encounter, requestDetails);
assertTrue(outcome.getCreated());

if (theUpdateObs) {
observation.setId(obsId.toUnqualifiedVersionless());
observation.setIssued(new Date());
myObservationDao.update(observation, requestDetails);
}

// test
outcome = myObservationDao.delete(obsId.toUnqualifiedVersionless(), requestDetails);

// validate
assertTrue(outcome.getOperationOutcome() instanceof OperationOutcome);
OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome();
assertFalse(oo.getIssue().isEmpty());
assertTrue(oo.getIssue().stream().anyMatch(i -> i.getDiagnostics().contains("Successfully deleted 1 resource(s)")));
}

@ParameterizedTest
@ValueSource(booleans = { true, false })
public void delete_resourceReferencedByNonVersionReferenceOnly_fails(boolean theUpdateObservation) {
// setup
SystemRequestDetails requestDetails = new SystemRequestDetails();
DaoMethodOutcome outcome;

setupResourceReferenceTests();

Observation observation = new Observation();
observation.setStatus(Observation.ObservationStatus.FINAL);
outcome = myObservationDao.create(observation, requestDetails);
IIdType obsId = outcome.getId();

// create encounter that references Observation by versioned reference only
Encounter encounter = new Encounter();
encounter.setStatus(Encounter.EncounterStatus.FINISHED);
Coding coding = new Coding();
coding.setSystem("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode");
coding.setCode("AMB");
encounter.setClass_(coding);
encounter.addReasonReference()
.setReference(obsId.toUnqualifiedVersionless().getValue()); // versionless reference
outcome = myEncounterDao.create(encounter, requestDetails);
assertTrue(outcome.getCreated());

if (theUpdateObservation) {
observation.setId(obsId.toUnqualifiedVersionless());
observation.setIssued(new Date());
myObservationDao.update(observation, requestDetails);
}

// test
try {
myObservationDao.delete(obsId.toUnqualifiedVersionless(), requestDetails);
fail("Deletion of resource referenced by versionless id should fail.");
} catch (ResourceVersionConflictException ex) {
assertTrue(ex.getLocalizedMessage().contains("Unable to delete")
&& ex.getLocalizedMessage().contains("at least one resource has a reference to this resource"),
ex.getLocalizedMessage());
}
}

@Test
public void delete_resourceReferencedByNonVersionedAndVersionedReferences_fails() {
// setup
SystemRequestDetails requestDetails = new SystemRequestDetails();
DaoMethodOutcome outcome;

setupResourceReferenceTests();

Observation observation = new Observation();
observation.setStatus(Observation.ObservationStatus.FINAL);
outcome = myObservationDao.create(observation, requestDetails);
IIdType obsId = outcome.getId();

// create 2 encounters; one referenced with a versionless id, one referenced with a versioned id
for (String id : new String[] { obsId.getValue(), obsId.toUnqualifiedVersionless().getValue() }) {
Encounter encounter = new Encounter();
encounter.setStatus(Encounter.EncounterStatus.FINISHED);
Coding coding = new Coding();
coding.setSystem("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode");
coding.setCode("AMB");
encounter.setClass_(coding);
encounter.addReasonReference()
.setReference(id);
outcome = myEncounterDao.create(encounter, requestDetails);
assertTrue(outcome.getCreated());
}

// test
try {
// versionless delete
myObservationDao.delete(obsId.toUnqualifiedVersionless(), requestDetails);
fail("Should not be able to delete observations referenced by versionless id because it is referenced by version AND by versionless.");
} catch (ResourceVersionConflictException ex) {
assertTrue(ex.getLocalizedMessage().contains("Unable to delete")
&& ex.getLocalizedMessage().contains("at least one resource has a reference to this resource"),
ex.getLocalizedMessage());
}
}

@Test
public void testNoDuplicateConstraintReferences() {
Patient patient = new Patient();
Expand Down

0 comments on commit 4c0a71f

Please sign in to comment.