diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4483-OverridePathBasedReferentialIntegrityForDeletesInterceptor-partitioned.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4483-OverridePathBasedReferentialIntegrityForDeletesInterceptor-partitioned.yaml new file mode 100644 index 00000000000..fc5877b09d0 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4483-OverridePathBasedReferentialIntegrityForDeletesInterceptor-partitioned.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 4483 +title: "The OverridePathBasedReferentialIntegrityForDeletesInterceptor now works on partitioned + servers. Thanks to GitHub user @JorisHeadease for the pull request!" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.java index 7d9bab00e83..ea80b7527bc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; @@ -99,7 +100,7 @@ public class OverridePathBasedReferentialIntegrityForDeletesInterceptor { * Interceptor hook method. Do not invoke directly. */ @Hook(value = Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, order = CascadingDeleteInterceptor.OVERRIDE_PATH_BASED_REF_INTEGRITY_INTERCEPTOR_ORDER) - public void handleDeleteConflicts(DeleteConflictList theDeleteConflictList) { + public void handleDeleteConflicts(DeleteConflictList theDeleteConflictList, RequestDetails requestDetails) { for (DeleteConflict nextConflict : theDeleteConflictList) { ourLog.info("Ignoring referential integrity deleting {} - Referred to from {} at path {}", nextConflict.getTargetId(), nextConflict.getSourceId(), nextConflict.getSourcePath()); @@ -107,7 +108,7 @@ public class OverridePathBasedReferentialIntegrityForDeletesInterceptor { IdDt targetId = nextConflict.getTargetId(); String targetIdValue = targetId.toVersionless().getValue(); - IBaseResource sourceResource = myDaoRegistry.getResourceDao(sourceId.getResourceType()).read(sourceId); + IBaseResource sourceResource = myDaoRegistry.getResourceDao(sourceId.getResourceType()).read(sourceId, requestDetails); IFhirPath fhirPath = myFhirContext.newFhirPath(); for (String nextPath : myPaths) { diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/MultitenantOverridePathBasedReferentialIntegrityForDeletesInterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/MultitenantOverridePathBasedReferentialIntegrityForDeletesInterceptorTest.java new file mode 100644 index 00000000000..43040fd6f32 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/MultitenantOverridePathBasedReferentialIntegrityForDeletesInterceptorTest.java @@ -0,0 +1,145 @@ +package ca.uhn.fhir.jpa.interceptor; + +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.provider.r4.BaseMultitenantResourceProviderR4Test; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +/** + *

Multitenant test version of {@link OverridePathBasedReferentialIntegrityForDeletesInterceptorTest}.

+ * + *

Ensures the tenant is properly propagated down to the {@link ca.uhn.fhir.jpa.api.dao.DaoRegistry} when requesting + * the conflicting resource.

+ * + *

This test runs a subset of tests from {@link OverridePathBasedReferentialIntegrityForDeletesInterceptorTest} + * against the {@link BaseMultitenantResourceProviderR4Test}

+ */ +public class MultitenantOverridePathBasedReferentialIntegrityForDeletesInterceptorTest extends BaseMultitenantResourceProviderR4Test { + + @Autowired + private OverridePathBasedReferentialIntegrityForDeletesInterceptor mySvc; + + @Autowired + private CascadingDeleteInterceptor myCascadingDeleteInterceptor; + + RequestDetails requestDetails = new SystemRequestDetails(); + + @BeforeEach + public void beforeEach() { + requestDetails.setTenantId(TENANT_A); + } + + @AfterEach + public void after() throws Exception { + myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.NOT_ALLOWED); + assertFalse(myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()); + + myInterceptorRegistry.unregisterInterceptor(mySvc); + mySvc.clearPaths(); + + super.after(); + } + + @Test + public void testAllowDelete() { + + mySvc.addPath("AuditEvent.agent.who"); + myInterceptorRegistry.registerInterceptor(mySvc); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + myPatientDao.update(patient, requestDetails); + + AuditEvent audit = new AuditEvent(); + audit.setId("A"); + audit.addAgent().getWho().setReference("Patient/P"); + myAuditEventDao.update(audit, requestDetails); + + // Delete should proceed + myPatientDao.delete(new IdType("Patient/P"), requestDetails); + + // Make sure we're deleted + try { + myPatientDao.read(new IdType("Patient/P"), requestDetails); + fail(); + } catch (ResourceGoneException e) { + // good + } + + // Search should still work + IBundleProvider searchOutcome = myAuditEventDao.search(SearchParameterMap.newSynchronous(AuditEvent.SP_AGENT, new ReferenceParam("Patient/P")), requestDetails); + assertEquals(1, searchOutcome.size()); + } + + @Test + public void testWrongPath() { + mySvc.addPath("AuditEvent.identifier"); + mySvc.addPath("Patient.agent.who"); + myInterceptorRegistry.registerInterceptor(mySvc); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + myPatientDao.update(patient, requestDetails); + + AuditEvent audit = new AuditEvent(); + audit.setId("A"); + audit.addAgent().getWho().setReference("Patient/P"); + myAuditEventDao.update(audit, requestDetails); + + // Delete should proceed + try { + myPatientDao.delete(new IdType("Patient/P"), requestDetails); + fail(); + } catch (ResourceVersionConflictException e) { + // good + } + + + } + + @Test + public void testCombineWithCascadeDeleteInterceptor() { + try { + myInterceptorRegistry.registerInterceptor(myCascadingDeleteInterceptor); + + mySvc.addPath("AuditEvent.agent.who"); + myInterceptorRegistry.registerInterceptor(mySvc); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + myPatientDao.update(patient, requestDetails); + + AuditEvent audit = new AuditEvent(); + audit.setId("A"); + audit.addAgent().getWho().setReference("Patient/P"); + myAuditEventDao.update(audit, requestDetails); + + // Delete should proceed + myPatientDao.delete(new IdType("Patient/P"), requestDetails); + + } finally { + myInterceptorRegistry.unregisterInterceptor(myCascadingDeleteInterceptor); + } + + } + +} diff --git a/pom.xml b/pom.xml index 000451e375f..50cbf482b70 100644 --- a/pom.xml +++ b/pom.xml @@ -852,6 +852,9 @@ John D'Amore More Informatics + + JorisHeadease +