Added Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE.
Also added a couple of tests.
This commit is contained in:
parent
160b221f5f
commit
fe21dba4a6
|
@ -67,8 +67,9 @@ public interface IInterceptorService extends IInterceptorBroadcaster {
|
|||
* Unregister an interceptor. This method has no effect if the given interceptor is not already registered.
|
||||
*
|
||||
* @param theInterceptor The interceptor to unregister
|
||||
* @return Returns <code>true</code> if the interceptor was found and removed
|
||||
*/
|
||||
void unregisterInterceptor(Object theInterceptor);
|
||||
boolean unregisterInterceptor(Object theInterceptor);
|
||||
|
||||
void registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor);
|
||||
|
||||
|
|
|
@ -1200,6 +1200,45 @@ public enum Pointcut {
|
|||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||
),
|
||||
|
||||
/**
|
||||
* Invoked before expunge is called on a resource.
|
||||
* <p>
|
||||
* Hooks will be passed a reference to a counter containing the current number of records that have been deleted.
|
||||
* If the hook deletes any records, the hook is expected to increment this counter by the number of records deleted.
|
||||
* </p>
|
||||
* Hooks may accept the following parameters:
|
||||
* <ul>
|
||||
* <li>java.util.concurrent.atomic.AtomicInteger - The counter holding the number of records deleted.</li>
|
||||
* <li>ca.uhn.fhir.model.primitive.IdDt - The id of the resource to be deleted. Note that version may be null.</li>
|
||||
* <li>
|
||||
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the servlet request. Note that the bean
|
||||
* properties are not all guaranteed to be populated, depending on how early during processing the
|
||||
* exception occurred.
|
||||
* </li>
|
||||
* <li>
|
||||
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
|
||||
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Hooks should return void.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
STORAGE_PRESTORAGE_EXPUNGE_RESOURCE (
|
||||
// Return type
|
||||
void.class,
|
||||
// Params
|
||||
"java.util.concurrent.atomic.AtomicInteger",
|
||||
"ca.uhn.fhir.model.primitive.IdDt",
|
||||
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||
),
|
||||
|
||||
/**
|
||||
* Note that this is a performance tracing hook. Use with caution in production
|
||||
* systems, since calling it may (or may not) carry a cost.
|
||||
|
|
|
@ -208,11 +208,12 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
|
|||
}
|
||||
|
||||
@Override
|
||||
public void unregisterInterceptor(Object theInterceptor) {
|
||||
public boolean unregisterInterceptor(Object theInterceptor) {
|
||||
synchronized (myRegistryMutex) {
|
||||
myInterceptors.removeIf(t -> t == theInterceptor);
|
||||
myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
|
||||
removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,15 @@ package ca.uhn.fhir.jpa.dao.expunge;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -31,20 +37,21 @@ import org.springframework.data.domain.Slice;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import javax.persistence.Id;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class ExpungeRun implements Callable<ExpungeOutcome> {
|
||||
public class ExpungeOperation implements Callable<ExpungeOutcome> {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ExpungeService.class);
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myPlatformTransactionManager;
|
||||
@Autowired
|
||||
private IResourceExpungeService myExpungeDaoService;
|
||||
@Autowired
|
||||
private PartitionRunner myPartitionRunner;
|
||||
@Autowired
|
||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
private final String myResourceName;
|
||||
private final Long myResourceId;
|
||||
|
@ -53,7 +60,7 @@ public class ExpungeRun implements Callable<ExpungeOutcome> {
|
|||
private final RequestDetails myRequestDetails;
|
||||
private final AtomicInteger myRemainingCount;
|
||||
|
||||
public ExpungeRun(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions, RequestDetails theRequestDetails) {
|
||||
public ExpungeOperation(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions, RequestDetails theRequestDetails) {
|
||||
myResourceName = theResourceName;
|
||||
myResourceId = theResourceId;
|
||||
myVersion = theVersion;
|
||||
|
@ -64,6 +71,15 @@ public class ExpungeRun implements Callable<ExpungeOutcome> {
|
|||
|
||||
@Override
|
||||
public ExpungeOutcome call() {
|
||||
final IdDt id;
|
||||
|
||||
callHooks();
|
||||
|
||||
if (expungeLimitReached()) {
|
||||
return expungeOutcome();
|
||||
}
|
||||
|
||||
|
||||
if (myExpungeOptions.isExpungeDeletedResources() && myVersion == null) {
|
||||
expungeDeletedResources();
|
||||
if (expungeLimitReached()) {
|
||||
|
@ -81,6 +97,30 @@ public class ExpungeRun implements Callable<ExpungeOutcome> {
|
|||
return expungeOutcome();
|
||||
}
|
||||
|
||||
private void callHooks() {
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
if (myResourceId == null) {
|
||||
return;
|
||||
}
|
||||
IdDt id;
|
||||
if (myVersion == null) {
|
||||
id = new IdDt(myResourceName, myResourceId);
|
||||
} else {
|
||||
id = new IdDt(myResourceName, myResourceId.toString(), myVersion.toString());
|
||||
}
|
||||
|
||||
// Notify Interceptors about pre-action call
|
||||
HookParams hooks = new HookParams()
|
||||
.add(AtomicInteger.class, counter)
|
||||
.add(IdDt.class, id)
|
||||
.add(RequestDetails.class, myRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, myRequestDetails);
|
||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequestDetails, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, hooks);
|
||||
|
||||
myRemainingCount.addAndGet(-1 * counter.get());
|
||||
}
|
||||
|
||||
private void expungeDeletedResources() {
|
||||
Slice<Long> resourceIds = findHistoricalVersionsOfDeletedResources();
|
||||
|
|
@ -44,7 +44,7 @@ public abstract class ExpungeService {
|
|||
private IResourceExpungeService myExpungeDaoService;
|
||||
|
||||
@Lookup
|
||||
protected abstract ExpungeRun getExpungeRun(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions, RequestDetails theRequestDetails);
|
||||
protected abstract ExpungeOperation getExpungeOperation(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions, RequestDetails theRequestDetails);
|
||||
|
||||
public ExpungeOutcome expunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions, RequestDetails theRequest) {
|
||||
ourLog.info("Expunge: ResourceName[{}] Id[{}] Version[{}] Options[{}]", theResourceName, theResourceId, theVersion, theExpungeOptions);
|
||||
|
@ -63,8 +63,8 @@ public abstract class ExpungeService {
|
|||
}
|
||||
}
|
||||
|
||||
ExpungeRun expungeRun = getExpungeRun(theResourceName, theResourceId, theVersion, theExpungeOptions, theRequest);
|
||||
return expungeRun.call();
|
||||
ExpungeOperation expungeOperation = getExpungeOperation(theResourceName, theResourceId, theVersion, theExpungeOptions, theRequest);
|
||||
return expungeOperation.call();
|
||||
}
|
||||
|
||||
public void deleteAllSearchParams(Long theResourceId) {
|
||||
|
|
|
@ -154,8 +154,8 @@ class ResourceExpungeService implements IResourceExpungeService {
|
|||
}
|
||||
}
|
||||
|
||||
private void expungeCurrentVersionOfResource(Long myResourceId, AtomicInteger theRemainingCount) {
|
||||
ResourceTable resource = myResourceTableDao.findById(myResourceId).orElseThrow(IllegalStateException::new);
|
||||
private void expungeCurrentVersionOfResource(Long theResourceId, AtomicInteger theRemainingCount) {
|
||||
ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalStateException::new);
|
||||
|
||||
ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersion(resource.getId(), resource.getVersion());
|
||||
if (currentVersion != null) {
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package ca.uhn.fhir.jpa.dao.expunge;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.config.TestDstu3Config;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
|
||||
import ca.uhn.fhir.jpa.model.concurrency.PointcutLatch;
|
||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {TestDstu3Config.class})
|
||||
public class ExpungeHookTest {
|
||||
@Autowired
|
||||
private IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||
@Autowired
|
||||
private ExpungeService myExpungeService;
|
||||
@Autowired
|
||||
private IInterceptorService myInterceptorService;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
PointcutLatch myEverythingLatch = new PointcutLatch(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING);
|
||||
PointcutLatch myExpungeResourceLatch = new PointcutLatch(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myDaoConfig.setExpungeEnabled(true);
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, myEverythingLatch);
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myExpungeResourceLatch);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
assertTrue(myInterceptorService.unregisterInterceptor(myEverythingLatch));
|
||||
assertTrue(myInterceptorService.unregisterInterceptor(myExpungeResourceLatch));
|
||||
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expungeEverythingHook() throws InterruptedException {
|
||||
IIdType id = myPatientDao.create(new Patient()).getId();
|
||||
assertNotNull(myPatientDao.read(id));
|
||||
myEverythingLatch.setExpectedCount(1);
|
||||
ExpungeOptions options = new ExpungeOptions();
|
||||
options.setExpungeEverything(true);
|
||||
myExpungeService.expunge(null, null, null, options, null);
|
||||
myEverythingLatch.awaitExpected();
|
||||
try {
|
||||
myPatientDao.read(id);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertThat(e.getMessage(), containsString("is not known"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expungeResourceHook() throws InterruptedException {
|
||||
IIdType expungeId = myPatientDao.create(new Patient()).getId();
|
||||
assertNotNull(myPatientDao.read(expungeId));
|
||||
myExpungeResourceLatch.setExpectedCount(1);
|
||||
myPatientDao.delete(expungeId);
|
||||
|
||||
ExpungeOptions options = new ExpungeOptions();
|
||||
options.setExpungeDeletedResources(true);
|
||||
myExpungeService.expunge("Patient", expungeId.getIdPartAsLong(), expungeId.getVersionIdPartAsLong(), options, null);
|
||||
HookParams hookParams = myExpungeResourceLatch.awaitExpected().get(0);
|
||||
IdDt hookId = hookParams.get(IdDt.class);
|
||||
assertEquals(expungeId.getValue(), hookId.getValue());
|
||||
}
|
||||
}
|
|
@ -30,8 +30,10 @@ public class FhirObjectPrinter implements Function<Object, String> {
|
|||
if (object instanceof IBaseResource) {
|
||||
IBaseResource resource = (IBaseResource) object;
|
||||
return resource.getClass().getSimpleName() + " { " + resource.getIdElement().getValue() + " }";
|
||||
} else {
|
||||
} else if (object != null) {
|
||||
return object.toString();
|
||||
} else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue