Rewrite deferred interceptor callback framework (#2121)
* Rewrite deferred interceptor callback framework * Improve deferred handling * Null guard * Test fix
This commit is contained in:
parent
ebbdafb107
commit
d2aae361bf
|
@ -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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
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.
|
* won't be indexed and searches won't work.
|
||||||
* @param theRequestDetails TODO
|
* @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);
|
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
|
* This method does not throw an exception if there are delete conflicts, but populates them
|
||||||
* in the provided list
|
* 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
|
* 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);
|
T readByPid(ResourcePersistentId thePid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param theId
|
|
||||||
* @param theRequestDetails TODO
|
* @param theRequestDetails TODO
|
||||||
* @throws ResourceNotFoundException If the ID is not known to the server
|
* @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
|
* @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)
|
* 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!
|
* Not supported in DSTU1!
|
||||||
|
|
|
@ -113,8 +113,6 @@ import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
@ -1258,7 +1256,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
.add(TransactionDetails.class, theTransactionDetails);
|
||||||
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, hookParams);
|
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, hookParams);
|
||||||
|
|
||||||
// Perform update
|
// Perform update
|
||||||
ResourceTable savedEntity = updateEntity(theRequestDetails, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, theForceUpdateVersion, thePerformIndexing);
|
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
|
// Notify interceptors
|
||||||
if (!savedEntity.isUnchangedInCurrentOperation()) {
|
if (!savedEntity.isUnchangedInCurrentOperation()) {
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
hookParams = new HookParams()
|
||||||
@Override
|
.add(IBaseResource.class, theOldResource)
|
||||||
public void beforeCommit(boolean readOnly) {
|
.add(IBaseResource.class, theResource)
|
||||||
HookParams hookParams = new HookParams()
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
.add(IBaseResource.class, theOldResource)
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||||
.add(IBaseResource.class, theResource)
|
.add(TransactionDetails.class, theTransactionDetails);
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, hookParams);
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
|
||||||
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, hookParams);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return savedEntity;
|
return savedEntity;
|
||||||
|
@ -1461,8 +1454,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
|
|
||||||
StringBuilder retVal = new StringBuilder();
|
StringBuilder retVal = new StringBuilder();
|
||||||
List<IPrimitiveType<String>> childElements = theContext.newTerser().getAllPopulatedChildElementsOfType(theResource, stringType);
|
List<IPrimitiveType<String>> childElements = theContext.newTerser().getAllPopulatedChildElementsOfType(theResource, stringType);
|
||||||
for (@SuppressWarnings("rawtypes")
|
for (IPrimitiveType<String> nextType : childElements) {
|
||||||
IPrimitiveType<String> nextType : childElements) {
|
|
||||||
if (stringType.equals(nextType.getClass())) {
|
if (stringType.equals(nextType.getClass())) {
|
||||||
String nextValue = nextType.getValueAsString();
|
String nextValue = nextType.getValueAsString();
|
||||||
if (isNotBlank(nextValue)) {
|
if (isNotBlank(nextValue)) {
|
||||||
|
|
|
@ -207,7 +207,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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));
|
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)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
.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
|
// Perform actual DB update
|
||||||
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, false, thePerformIndexing);
|
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
|
// Notify JPA interceptors
|
||||||
if (!updatedEntity.isUnchangedInCurrentOperation()) {
|
if (!updatedEntity.isUnchangedInCurrentOperation()) {
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
hookParams = new HookParams()
|
||||||
@Override
|
.add(IBaseResource.class, theResource)
|
||||||
public void beforeCommit(boolean readOnly) {
|
.add(RequestDetails.class, theRequest)
|
||||||
HookParams hookParams = new HookParams()
|
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||||
.add(IBaseResource.class, theResource)
|
.add(TransactionDetails.class, theTransactionDetails);
|
||||||
.add(RequestDetails.class, theRequest)
|
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams);
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
|
||||||
doCallHooks(theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(true);
|
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(true);
|
||||||
|
@ -400,7 +395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
validateIdPresentForDelete(theId);
|
||||||
validateDeleteEnabled();
|
validateDeleteEnabled();
|
||||||
|
|
||||||
|
@ -439,7 +434,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
.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);
|
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequestDetails, theTransactionDetails);
|
||||||
|
|
||||||
|
@ -455,17 +450,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
resourceToDelete.setId(entity.getIdDt());
|
resourceToDelete.setId(entity.getIdDt());
|
||||||
|
|
||||||
// Notify JPA interceptors
|
// Notify JPA interceptors
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
HookParams hookParams = new HookParams()
|
||||||
@Override
|
.add(IBaseResource.class, resourceToDelete)
|
||||||
public void beforeCommit(boolean readOnly) {
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
HookParams hookParams = new HookParams()
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||||
.add(IBaseResource.class, resourceToDelete)
|
.add(TransactionDetails.class, theTransactionDetails);
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts()) {
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
theTransactionDetails.addDeferredInterceptorBroadcast(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
} else {
|
||||||
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
|
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true);
|
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true);
|
||||||
|
|
||||||
|
@ -516,7 +510,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@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();
|
StopWatch w = new StopWatch();
|
||||||
TransactionDetails transactionDetails = new TransactionDetails();
|
TransactionDetails transactionDetails = new TransactionDetails();
|
||||||
List<ResourceTable> deletedResources = new ArrayList<>();
|
List<ResourceTable> deletedResources = new ArrayList<>();
|
||||||
|
@ -529,23 +523,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
// Notify IServerOperationInterceptors about pre-action call
|
// Notify IServerOperationInterceptors about pre-action call
|
||||||
HookParams hooks = new HookParams()
|
HookParams hooks = new HookParams()
|
||||||
.add(IBaseResource.class, resourceToDelete)
|
.add(IBaseResource.class, resourceToDelete)
|
||||||
.add(RequestDetails.class, theTheRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theTheRequest)
|
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||||
.add(TransactionDetails.class, transactionDetails);
|
.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
|
// Notify interceptors
|
||||||
IdDt idToDelete = entity.getIdDt();
|
IdDt idToDelete = entity.getIdDt();
|
||||||
if (theTheRequest != null) {
|
if (theRequest != null) {
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theTheRequest, idToDelete.getResourceType(), idToDelete);
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
|
||||||
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
|
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform delete
|
// Perform delete
|
||||||
|
|
||||||
updateEntityForDelete(theTheRequest, transactionDetails, entity);
|
updateEntityForDelete(theRequest, transactionDetails, entity);
|
||||||
resourceToDelete.setId(entity.getIdDt());
|
resourceToDelete.setId(entity.getIdDt());
|
||||||
|
|
||||||
// Notify JPA interceptors
|
// Notify JPA interceptors
|
||||||
|
@ -554,10 +548,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
public void beforeCommit(boolean readOnly) {
|
public void beforeCommit(boolean readOnly) {
|
||||||
HookParams hookParams = new HookParams()
|
HookParams hookParams = new HookParams()
|
||||||
.add(IBaseResource.class, resourceToDelete)
|
.add(IBaseResource.class, resourceToDelete)
|
||||||
.add(RequestDetails.class, theTheRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theTheRequest)
|
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||||
.add(TransactionDetails.class, transactionDetails);
|
.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
|
@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) {
|
if (theResource == null) {
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
|
|
|
@ -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.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
|
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
|
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.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.param.QualifierDetails;
|
import ca.uhn.fhir.rest.param.QualifierDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -184,8 +185,12 @@ public abstract class BaseStorageDao {
|
||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doCallHooks(RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) {
|
protected void doCallHooks(TransactionDetails theTransactionDetails, RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) {
|
||||||
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
|
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts(thePointcut)) {
|
||||||
|
theTransactionDetails.addDeferredInterceptorBroadcast(thePointcut, theParams);
|
||||||
|
} else {
|
||||||
|
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IInterceptorBroadcaster getInterceptorBroadcaster();
|
protected abstract IInterceptorBroadcaster getInterceptorBroadcaster();
|
||||||
|
|
|
@ -68,6 +68,7 @@ import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
import org.hl7.fhir.exceptions.FHIRException;
|
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,
|
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) {
|
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
||||||
|
|
||||||
if (theRequest != null) {
|
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
|
||||||
theRequest.startDeferredOperationCallback();
|
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
|
||||||
}
|
Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED,
|
||||||
|
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Set<String> deletedResources = new HashSet<>();
|
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);
|
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;
|
return entriesToProcess;
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (theRequest != null) {
|
if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts()) {
|
||||||
theRequest.stopDeferredRequestOperationCallbackAndRunDeferredItems();
|
theTransactionDetails.endAcceptingDeferredInterceptorBroadcasts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.api.model.DeleteConflictList;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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 ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
|
@ -243,11 +244,12 @@ public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeleteConflictOutcome deleteConflictsFixedRetryCount(DeleteConflictList theList) {
|
private DeleteConflictOutcome deleteConflictsFixedRetryCount(DeleteConflictList theList) {
|
||||||
|
TransactionDetails transactionDetails = new TransactionDetails();
|
||||||
for (DeleteConflict next : theList) {
|
for (DeleteConflict next : theList) {
|
||||||
IdDt source = next.getSourceId();
|
IdDt source = next.getSourceId();
|
||||||
if ("Patient".equals(source.getResourceType())) {
|
if ("Patient".equals(source.getResourceType())) {
|
||||||
ourLog.info("Deleting {}", source);
|
ourLog.info("Deleting {}", source);
|
||||||
myPatientDao.delete(source, theList, null, null);
|
myPatientDao.delete(source, theList, null, transactionDetails);
|
||||||
++myInterceptorDeleteCount;
|
++myInterceptorDeleteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
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 ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -95,10 +96,11 @@ public class EmpiPersonDeletingSvc {
|
||||||
|
|
||||||
private void deleteConflictBatch(DeleteConflictList theDcl, IFhirResourceDao<IBaseResource> theDao) {
|
private void deleteConflictBatch(DeleteConflictList theDcl, IFhirResourceDao<IBaseResource> theDao) {
|
||||||
DeleteConflictList newBatch = new DeleteConflictList();
|
DeleteConflictList newBatch = new DeleteConflictList();
|
||||||
|
TransactionDetails transactionDetails = new TransactionDetails();
|
||||||
for (DeleteConflict next : theDcl) {
|
for (DeleteConflict next : theDcl) {
|
||||||
IdDt nextSource = next.getSourceId();
|
IdDt nextSource = next.getSourceId();
|
||||||
ourLog.info("Have delete conflict {} - Cascading delete", nextSource);
|
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.removeAll();
|
||||||
theDcl.addAll(newBatch);
|
theDcl.addAll(newBatch);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package ca.uhn.fhir.rest.api.server;
|
package ca.uhn.fhir.rest.api.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
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.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -64,7 +61,6 @@ public abstract class RequestDetails {
|
||||||
private String myOperation;
|
private String myOperation;
|
||||||
private Map<String, String[]> myParameters;
|
private Map<String, String[]> myParameters;
|
||||||
private byte[] myRequestContents;
|
private byte[] myRequestContents;
|
||||||
private DeferredOperationCallback myDeferredInterceptorBroadcaster;
|
|
||||||
private String myRequestPath;
|
private String myRequestPath;
|
||||||
private RequestTypeEnum myRequestType;
|
private RequestTypeEnum myRequestType;
|
||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
|
@ -305,9 +301,6 @@ public abstract class RequestDetails {
|
||||||
* all interceptors
|
* all interceptors
|
||||||
*/
|
*/
|
||||||
public IInterceptorBroadcaster getInterceptorBroadcaster() {
|
public IInterceptorBroadcaster getInterceptorBroadcaster() {
|
||||||
if (myDeferredInterceptorBroadcaster != null) {
|
|
||||||
return myDeferredInterceptorBroadcaster;
|
|
||||||
}
|
|
||||||
return myInterceptorBroadcaster;
|
return myInterceptorBroadcaster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,26 +493,6 @@ public abstract class RequestDetails {
|
||||||
myRequestContents = theRequestContents;
|
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() {
|
public String getTransactionGuid() {
|
||||||
return myTransactionGuid;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,16 @@ package ca.uhn.fhir.rest.api.server.storage;
|
||||||
* #L%
|
* #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 org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -34,7 +40,7 @@ import java.util.function.Supplier;
|
||||||
* (i.e. a FHIR create, read, transaction, etc.).
|
* (i.e. a FHIR create, read, transaction, etc.).
|
||||||
* <p>
|
* <p>
|
||||||
* The intent with this class is to hold things we want to pass from operation to operation within a transaction in
|
* 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>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 5.0.0
|
* @since 5.0.0
|
||||||
|
@ -44,12 +50,14 @@ public class TransactionDetails {
|
||||||
private final Date myTransactionDate;
|
private final Date myTransactionDate;
|
||||||
private Map<IIdType, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
private Map<IIdType, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||||
private Map<String, Object> myUserData;
|
private Map<String, Object> myUserData;
|
||||||
|
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
||||||
|
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public TransactionDetails() {
|
public TransactionDetails() {
|
||||||
myTransactionDate = new Date();
|
this(new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,5 +134,57 @@ public class TransactionDetails {
|
||||||
}
|
}
|
||||||
return retVal;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue