mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-17 02:15:22 +00:00
wip
This commit is contained in:
parent
28c917b7a2
commit
ac8e012892
@ -1513,10 +1513,10 @@ public enum Pointcut implements IPointcut {
|
||||
|
||||
/**
|
||||
* <b>Storage Hook:</b>
|
||||
* Invoked before a resource will be deleted
|
||||
* Invoked after all entries in a transaction bundle have been executed
|
||||
* <p>
|
||||
* Hooks will have access to the contents of the resource being deleted
|
||||
* but should not make any changes as storage has already occurred
|
||||
* Hooks will have access to the original bundle, as well as all the deferred interceptor broadcasts related to the
|
||||
* processing of the transaction bundle
|
||||
* </p>
|
||||
* Hooks may accept the following parameters:
|
||||
* <ul>
|
||||
@ -1537,6 +1537,9 @@ public enum Pointcut implements IPointcut {
|
||||
* <li>
|
||||
* ca.uhn.fhir.rest.api.server.storage.TransactionDetails - The outer transaction details object (since 5.0.0)
|
||||
* </li>
|
||||
* <li>
|
||||
* ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts- A collection of pointcut invocations and their parameters which were deferred.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Hooks should return <code>void</code>.
|
||||
|
@ -48,6 +48,7 @@ import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
@ -1025,6 +1026,12 @@ public abstract class BaseTransactionProcessor {
|
||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, nextPointcut, nextParams);
|
||||
}
|
||||
|
||||
DeferredInterceptorBroadcasts deferredInterceptorBroadcasts = new DeferredInterceptorBroadcasts(deferredBroadcastEvents);
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(DeferredInterceptorBroadcasts.class, deferredInterceptorBroadcasts);
|
||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_TRANSACTION_PROCESSED, params);
|
||||
|
||||
theTransactionDetails.deferredBroadcastProcessingFinished();
|
||||
|
||||
|
@ -0,0 +1,213 @@
|
||||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.test.concurrency.PointcutLatch;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.DiagnosticReport;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class TransactionHookTest extends BaseJpaR4SystemTest {
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnDelete(true);
|
||||
}
|
||||
|
||||
PointcutLatch myPointcutLatch = new PointcutLatch(Pointcut.STORAGE_TRANSACTION_PROCESSED);
|
||||
@Autowired
|
||||
private IInterceptorService myInterceptorService;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.STORAGE_TRANSACTION_PROCESSED, myPointcutLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHookShouldContainParamsForAllCreateUpdateDeleteInvocations() {
|
||||
final Observation obs1 = new Observation();
|
||||
obs1.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
|
||||
|
||||
final Observation obs2 = new Observation();
|
||||
obs2.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
|
||||
|
||||
final DiagnosticReport rpt = new DiagnosticReport();
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
myDiagnosticReportDao.read(rptId);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(obs2id.getValue());
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, b);
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
// good, transaction should not succeed because DiagnosticReport has a reference to the obs2
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInTransactionShouldSucceedWhenReferencesAreAlsoRemoved() {
|
||||
final Observation obs1 = new Observation();
|
||||
obs1.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
|
||||
|
||||
final Observation obs2 = new Observation();
|
||||
obs2.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
|
||||
|
||||
final DiagnosticReport rpt = new DiagnosticReport();
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
myDiagnosticReportDao.read(rptId);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(rptId.getValue());
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(obs2id.getValue());
|
||||
|
||||
try {
|
||||
// transaction should succeed because the DiagnosticReport which references obs2 is also deleted
|
||||
mySystemDao.transaction(mySrd, b);
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteWithHas_SourceModifiedToNoLongerIncludeReference() {
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
|
||||
|
||||
DiagnosticReport rpt = new DiagnosticReport();
|
||||
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
|
||||
rpt = new DiagnosticReport();
|
||||
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER");
|
||||
b.addEntry().setResource(rpt).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl("DiagnosticReport?identifier=foo|IDENTIFIER");
|
||||
mySystemDao.transaction(mySrd, b);
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
try {
|
||||
myObservationDao.read(obs2id);
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
rpt = myDiagnosticReportDao.read(rptId);
|
||||
assertThat(rpt.getResult(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteWithId_SourceModifiedToNoLongerIncludeReference() {
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
|
||||
|
||||
DiagnosticReport rpt = new DiagnosticReport();
|
||||
rpt.addResult(new Reference(obs1id));
|
||||
IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
|
||||
rpt = new DiagnosticReport();
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(obs1id.getValue());
|
||||
b.addEntry().setResource(rpt).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl(rptId.getValue());
|
||||
mySystemDao.transaction(mySrd, b);
|
||||
|
||||
myObservationDao.read(obs2id);
|
||||
myDiagnosticReportDao.read(rptId);
|
||||
try {
|
||||
myObservationDao.read(obs1id);
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteWithHas_SourceModifiedToStillIncludeReference() {
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(Observation.ObservationStatus.FINAL);
|
||||
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
|
||||
|
||||
DiagnosticReport rpt = new DiagnosticReport();
|
||||
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
|
||||
rpt = new DiagnosticReport();
|
||||
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
|
||||
rpt.addResult(new Reference(obs2id));
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER");
|
||||
b.addEntry().setResource(rpt).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl("DiagnosticReport?identifier=foo|IDENTIFIER");
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, b);
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e ) {
|
||||
assertThat(e.getMessage(), matchesPattern("Unable to delete Observation/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource DiagnosticReport/[0-9]+ in path DiagnosticReport.result"));
|
||||
}
|
||||
|
||||
myObservationDao.read(obs1id);
|
||||
myObservationDao.read(obs2id);
|
||||
myDiagnosticReportDao.read(rptId);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user