Rewrite deferred interceptor callback framework (#2121)

* Rewrite deferred interceptor callback framework

* Improve deferred handling

* Null guard

* Test fix
This commit is contained in:
James Agnew 2020-10-07 20:08:18 -04:00 committed by GitHub
parent ebbdafb107
commit d2aae361bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 140 deletions

View File

@ -46,6 +46,7 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
@ -81,7 +82,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* won't be indexed and searches won't work.
* @param theRequestDetails TODO
*/
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails);
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails);
DaoMethodOutcome create(T theResource, String theIfNoneExist, RequestDetails theRequestDetails);
@ -95,7 +96,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* This method does not throw an exception if there are delete conflicts, but populates them
* in the provided list
*/
DaoMethodOutcome delete(IIdType theResource, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails);
DaoMethodOutcome delete(IIdType theResource, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
/**
* This method throws an exception if there are delete conflicts
@ -167,7 +168,6 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
T readByPid(ResourcePersistentId thePid);
/**
* @param theId
* @param theRequestDetails TODO
* @throws ResourceNotFoundException If the ID is not known to the server
*/
@ -244,7 +244,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* @param theForceUpdateVersion Create a new version with the same contents as the current version even if the content hasn't changed (this is mostly useful for
* resources mapping to external content such as external code systems)
*/
DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails);
DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
/**
* Not supported in DSTU1!

View File

@ -113,8 +113,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
@ -1258,7 +1256,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, hookParams);
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, hookParams);
// Perform update
ResourceTable savedEntity = updateEntity(theRequestDetails, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, theForceUpdateVersion, thePerformIndexing);
@ -1283,18 +1281,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// Notify interceptors
if (!savedEntity.isUnchangedInCurrentOperation()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theOldResource)
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, hookParams);
}
});
hookParams = new HookParams()
.add(IBaseResource.class, theOldResource)
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, hookParams);
}
return savedEntity;
@ -1461,8 +1454,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
StringBuilder retVal = new StringBuilder();
List<IPrimitiveType<String>> childElements = theContext.newTerser().getAllPopulatedChildElementsOfType(theResource, stringType);
for (@SuppressWarnings("rawtypes")
IPrimitiveType<String> nextType : childElements) {
for (IPrimitiveType<String> nextType : childElements) {
if (stringType.equals(nextType.getClass())) {
String nextValue = nextType.getValueAsString();
if (isNotBlank(nextValue)) {

View File

@ -207,7 +207,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
return myTransactionService.execute(theRequestDetails, tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails));
}
@ -305,7 +305,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, hookParams);
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, hookParams);
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, false, thePerformIndexing);
@ -343,17 +343,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify JPA interceptors
if (!updatedEntity.isUnchangedInCurrentOperation()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams);
}
});
hookParams = new HookParams()
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams);
}
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(true);
@ -400,7 +395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails) {
validateIdPresentForDelete(theId);
validateDeleteEnabled();
@ -439,7 +434,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hook);
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hook);
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequestDetails, theTransactionDetails);
@ -455,17 +450,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts()) {
theTransactionDetails.addDeferredInterceptorBroadcast(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
} else {
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true);
@ -516,7 +510,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@NotNull
@Override
public DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theTheRequest) {
public DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
StopWatch w = new StopWatch();
TransactionDetails transactionDetails = new TransactionDetails();
List<ResourceTable> deletedResources = new ArrayList<>();
@ -529,23 +523,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify IServerOperationInterceptors about pre-action call
HookParams hooks = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theTheRequest)
.addIfMatchesType(ServletRequestDetails.class, theTheRequest)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theTheRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
doCallHooks(transactionDetails, theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theTheRequest, transactionDetails);
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequest, transactionDetails);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
if (theTheRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theTheRequest, idToDelete.getResourceType(), idToDelete);
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
// Perform delete
updateEntityForDelete(theTheRequest, transactionDetails, entity);
updateEntityForDelete(theRequest, transactionDetails, entity);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
@ -554,10 +548,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theTheRequest)
.addIfMatchesType(ServletRequestDetails.class, theTheRequest)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theTheRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
doCallHooks(transactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
}
@ -1359,7 +1353,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, @Nonnull TransactionDetails theTransactionDetails) {
if (theResource == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
throw new InvalidRequestException(msg);

View File

@ -40,6 +40,7 @@ import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.param.QualifierDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -184,8 +185,12 @@ public abstract class BaseStorageDao {
return outcome;
}
protected void doCallHooks(RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) {
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
protected void doCallHooks(TransactionDetails theTransactionDetails, RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) {
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts(thePointcut)) {
theTransactionDetails.addDeferredInterceptorBroadcast(thePointcut, theParams);
} else {
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
}
}
protected abstract IInterceptorBroadcaster getInterceptorBroadcaster();

View File

@ -68,6 +68,7 @@ import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.exceptions.FHIRException;
@ -540,9 +541,11 @@ public abstract class BaseTransactionProcessor {
private Map<IBase, IBasePersistedResource> doTransactionWriteOperations(final ServletRequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
if (theRequest != null) {
theRequest.startDeferredOperationCallback();
}
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
);
try {
Set<String> deletedResources = new HashSet<>();
@ -980,11 +983,19 @@ public abstract class BaseTransactionProcessor {
}
ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement);
}
ListMultimap<Pointcut, HookParams> deferredBroadcastEvents = theTransactionDetails.endAcceptingDeferredInterceptorBroadcasts();
for (Map.Entry<Pointcut, HookParams> nextEntry : deferredBroadcastEvents.entries()) {
Pointcut nextPointcut = nextEntry.getKey();
HookParams nextParams = nextEntry.getValue();
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, nextPointcut, nextParams);
}
return entriesToProcess;
} finally {
if (theRequest != null) {
theRequest.stopDeferredRequestOperationCallbackAndRunDeferredItems();
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts()) {
theTransactionDetails.endAcceptingDeferredInterceptorBroadcasts();
}
}
}

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.model.primitive.IdDt;
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.Condition;
@ -243,11 +244,12 @@ public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
}
private DeleteConflictOutcome deleteConflictsFixedRetryCount(DeleteConflictList theList) {
TransactionDetails transactionDetails = new TransactionDetails();
for (DeleteConflict next : theList) {
IdDt source = next.getSourceId();
if ("Patient".equals(source.getResourceType())) {
ourLog.info("Deleting {}", source);
myPatientDao.delete(source, theList, null, null);
myPatientDao.delete(source, theList, null, transactionDetails);
++myInterceptorDeleteCount;
}
}

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
@ -95,10 +96,11 @@ public class EmpiPersonDeletingSvc {
private void deleteConflictBatch(DeleteConflictList theDcl, IFhirResourceDao<IBaseResource> theDao) {
DeleteConflictList newBatch = new DeleteConflictList();
TransactionDetails transactionDetails = new TransactionDetails();
for (DeleteConflict next : theDcl) {
IdDt nextSource = next.getSourceId();
ourLog.info("Have delete conflict {} - Cascading delete", nextSource);
theDao.delete(nextSource.toVersionless(), newBatch, null, null);
theDao.delete(nextSource.toVersionless(), newBatch, null, transactionDetails);
}
theDcl.removeAll();
theDcl.addAll(newBatch);

View File

@ -1,9 +1,7 @@
package ca.uhn.fhir.rest.api.server;
import ca.uhn.fhir.context.FhirContext;
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.rest.api.Constants;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
@ -15,7 +13,6 @@ import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@ -64,7 +61,6 @@ public abstract class RequestDetails {
private String myOperation;
private Map<String, String[]> myParameters;
private byte[] myRequestContents;
private DeferredOperationCallback myDeferredInterceptorBroadcaster;
private String myRequestPath;
private RequestTypeEnum myRequestType;
private String myResourceName;
@ -305,9 +301,6 @@ public abstract class RequestDetails {
* all interceptors
*/
public IInterceptorBroadcaster getInterceptorBroadcaster() {
if (myDeferredInterceptorBroadcaster != null) {
return myDeferredInterceptorBroadcaster;
}
return myInterceptorBroadcaster;
}
@ -500,26 +493,6 @@ public abstract class RequestDetails {
myRequestContents = theRequestContents;
}
/**
* Sets the {@link #getInterceptorBroadcaster()} () interceptor broadcaster} handler in
* deferred mode, meaning that any notifications will be queued up for delivery, but
* won't be delivered until {@link #stopDeferredRequestOperationCallbackAndRunDeferredItems()}
* is called.
*/
public void startDeferredOperationCallback() {
myDeferredInterceptorBroadcaster = new DeferredOperationCallback(myInterceptorBroadcaster);
}
/**
* @see #startDeferredOperationCallback()
*/
public void stopDeferredRequestOperationCallbackAndRunDeferredItems() {
DeferredOperationCallback deferredCallback = myDeferredInterceptorBroadcaster;
deferredCallback.playDeferredActions();
myInterceptorBroadcaster = deferredCallback.getWrap();
myDeferredInterceptorBroadcaster = null;
}
public String getTransactionGuid() {
return myTransactionGuid;
}
@ -529,47 +502,4 @@ public abstract class RequestDetails {
}
private class DeferredOperationCallback implements IInterceptorBroadcaster {
private final IInterceptorBroadcaster myWrap;
private final List<Runnable> myDeferredTasks = new ArrayList<>();
private DeferredOperationCallback(@Nonnull IInterceptorBroadcaster theWrap) {
Validate.notNull(theWrap);
myWrap = theWrap;
}
void playDeferredActions() {
myDeferredTasks.forEach(Runnable::run);
}
IInterceptorBroadcaster getWrap() {
return myWrap;
}
@Override
public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
myDeferredTasks.add(() -> myWrap.callHooks(thePointcut, theParams));
return true;
}
@Override
public Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams) {
if (!thePointcut.getReturnType().equals(void.class)) {
return myWrap.callHooksAndReturnObject(thePointcut, theParams);
}
myDeferredTasks.add(() -> myWrap.callHooksAndReturnObject(thePointcut, theParams));
return null;
}
@Override
public boolean hasHooks(Pointcut thePointcut) {
return myWrap.hasHooks(thePointcut);
}
}
}

View File

@ -20,10 +20,16 @@ package ca.uhn.fhir.rest.api.server.storage;
* #L%
*/
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@ -34,7 +40,7 @@ import java.util.function.Supplier;
* (i.e. a FHIR create, read, transaction, etc.).
* <p>
* The intent with this class is to hold things we want to pass from operation to operation within a transaction in
* order to avoid looking things up multime times, etc.
* order to avoid looking things up multiple times, etc.
* </p>
*
* @since 5.0.0
@ -44,12 +50,14 @@ public class TransactionDetails {
private final Date myTransactionDate;
private Map<IIdType, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
private Map<String, Object> myUserData;
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
/**
* Constructor
*/
public TransactionDetails() {
myTransactionDate = new Date();
this(new Date());
}
/**
@ -126,5 +134,57 @@ public class TransactionDetails {
}
return retVal;
}
/**
* This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
*
* @since 5.2.0
*/
public void beginAcceptingDeferredInterceptorBroadcasts(Pointcut... thePointcuts) {
Validate.isTrue(!isAcceptingDeferredInterceptorBroadcasts());
myDeferredInterceptorBroadcasts = ArrayListMultimap.create();
myDeferredInterceptorBroadcastPointcuts = EnumSet.of(thePointcuts[0], thePointcuts);
}
/**
* This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
*
* @since 5.2.0
*/
public boolean isAcceptingDeferredInterceptorBroadcasts() {
return myDeferredInterceptorBroadcasts != null;
}
/**
* This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
*
* @since 5.2.0
*/
public boolean isAcceptingDeferredInterceptorBroadcasts(Pointcut thePointcut) {
return myDeferredInterceptorBroadcasts != null && myDeferredInterceptorBroadcastPointcuts.contains(thePointcut);
}
/**
* This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
*
* @since 5.2.0
*/
public ListMultimap<Pointcut, HookParams> endAcceptingDeferredInterceptorBroadcasts() {
Validate.isTrue(isAcceptingDeferredInterceptorBroadcasts());
ListMultimap<Pointcut, HookParams> retVal = myDeferredInterceptorBroadcasts;
myDeferredInterceptorBroadcasts = null;
myDeferredInterceptorBroadcastPointcuts = null;
return retVal;
}
/**
* This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
*
* @since 5.2.0
*/
public void addDeferredInterceptorBroadcast(Pointcut thePointcut, HookParams theHookParams) {
Validate.isTrue(isAcceptingDeferredInterceptorBroadcasts(thePointcut));
myDeferredInterceptorBroadcasts.put(thePointcut, theHookParams);
}
}