Changes to enable adjusting limits on delete conflict handling through configuration.
This commit is contained in:
parent
e22549e0c4
commit
a60e5809b2
|
@ -1244,7 +1244,7 @@ public enum Pointcut {
|
|||
|
||||
/**
|
||||
* <b>Storage Hook:</b>
|
||||
* Invoked before a resource will be created
|
||||
* Invoked before a resource will be deleted
|
||||
* <p>
|
||||
* Hooks will have access to the contents of the resource being deleted
|
||||
* but should not make any changes as storage has already occurred
|
||||
|
@ -1282,7 +1282,7 @@ public enum Pointcut {
|
|||
|
||||
/**
|
||||
* <b>Storage Hook:</b>
|
||||
* Invoked when a resource delete operation is about to fail due to referential integrity hcts.
|
||||
* Invoked when a resource delete operation is about to fail due to referential integrity checks. Intended for use with ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.
|
||||
* <p>
|
||||
* Hooks will have access to the list of resources that have references to the resource being deleted.
|
||||
* </p>
|
||||
|
|
|
@ -83,6 +83,9 @@ public class DaoConfig {
|
|||
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
|
||||
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800;
|
||||
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
|
||||
private static final int DEFAULT_MAXIMUM_DELETE_CONFLICT_COUNT = 60;
|
||||
private static final int DEFAULT_MAXIMUM_DELETE_CONFLICT_RETRY_ATTEMPTS = 10;
|
||||
|
||||
|
||||
/**
|
||||
* Child Configurations
|
||||
|
@ -153,6 +156,10 @@ public class DaoConfig {
|
|||
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
|
||||
private boolean myFilterParameterEnabled = false;
|
||||
private StoreMetaSourceInformationEnum myStoreMetaSourceInformation = StoreMetaSourceInformationEnum.SOURCE_URI_AND_REQUEST_ID;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private Integer myMaximumDeleteConflictQueryCount = DEFAULT_MAXIMUM_DELETE_CONFLICT_COUNT;
|
||||
/**
|
||||
* Do not change default of {@code true}!
|
||||
*
|
||||
|
@ -2021,4 +2028,33 @@ public class DaoConfig {
|
|||
*/
|
||||
ANY
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
|
||||
* </p>
|
||||
* <p>
|
||||
* The default value for this setting is {@code 60}.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public Integer getMaximumDeleteConflictQueryCount() {
|
||||
return myMaximumDeleteConflictQueryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
|
||||
* </p>
|
||||
* <p>
|
||||
* The default value for this setting is {@code 60}.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public void setMaximumDeleteConflictQueryCount(Integer theMaximumDeleteConflictQueryCount) {
|
||||
myMaximumDeleteConflictQueryCount = theMaximumDeleteConflictQueryCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -51,8 +52,8 @@ import java.util.List;
|
|||
public class DeleteConflictService {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictService.class);
|
||||
public static final int FIRST_QUERY_RESULT_COUNT = 1;
|
||||
public static final int RETRY_QUERY_RESULT_COUNT = 60;
|
||||
public static final int MAX_RETRY_ATTEMPTS = 10;
|
||||
public static int MAX_RETRY_ATTEMPTS = 10;
|
||||
public static String MAX_RETRY_ATTEMPTS_EXCEEDED_MSG = "Requested delete operation stopped before all conflicts were handled. May need to increase the configured Maximum Delete Conflict Query Count.";
|
||||
|
||||
@Autowired
|
||||
DeleteConflictFinderService myDeleteConflictFinderService;
|
||||
|
@ -81,11 +82,16 @@ public class DeleteConflictService {
|
|||
while (outcome != null) {
|
||||
int shouldRetryCount = Math.min(outcome.getShouldRetryCount(), MAX_RETRY_ATTEMPTS);
|
||||
if (!(retryCount < shouldRetryCount)) break;
|
||||
newConflicts = new DeleteConflictList();
|
||||
outcome = findAndHandleConflicts(theRequest, newConflicts, theEntity, theForValidate, RETRY_QUERY_RESULT_COUNT, theTransactionDetails);
|
||||
newConflicts = new DeleteConflictList(newConflicts);
|
||||
outcome = findAndHandleConflicts(theRequest, newConflicts, theEntity, theForValidate, myDaoConfig.getMaximumDeleteConflictQueryCount(), theTransactionDetails);
|
||||
++retryCount;
|
||||
}
|
||||
theDeleteConflicts.addAll(newConflicts);
|
||||
if(retryCount >= MAX_RETRY_ATTEMPTS && !newConflicts.isEmpty()) {
|
||||
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(myFhirContext);
|
||||
OperationOutcomeUtil.addIssue(myFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, MAX_RETRY_ATTEMPTS_EXCEEDED_MSG,null, "processing");
|
||||
throw new ResourceVersionConflictException(MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, oo);
|
||||
}
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
|
@ -162,4 +168,9 @@ public class DeleteConflictService {
|
|||
|
||||
throw new ResourceVersionConflictException(firstMsg, oo);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void setMaxRetryAttempts(Integer theMaxRetryAttempts) {
|
||||
MAX_RETRY_ATTEMPTS = theMaxRetryAttempts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.delete;
|
|||
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteConflict;
|
||||
|
@ -17,6 +18,7 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
@ -28,6 +30,9 @@ import static org.junit.Assert.*;
|
|||
public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictServiceR4Test.class);
|
||||
|
||||
@Autowired
|
||||
DaoConfig myDaoConfig;
|
||||
|
||||
private DeleteConflictInterceptor myDeleteInterceptor = new DeleteConflictInterceptor();
|
||||
private int myInterceptorDeleteCount;
|
||||
|
||||
|
@ -133,6 +138,48 @@ public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
|
|||
assertEquals(3, myInterceptorDeleteCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHookDeletesLargeNumberOfConflicts() {
|
||||
|
||||
Organization organization = new Organization();
|
||||
organization.setName("FOO");
|
||||
IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless();
|
||||
|
||||
// Create 12 conflicts.
|
||||
for (int j=0; j < 12 ; j++) {
|
||||
Patient patient = new Patient();
|
||||
patient.setManagingOrganization(new Reference(organizationId));
|
||||
myPatientDao.create(patient).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
DeleteConflictService.setMaxRetryAttempts(3);
|
||||
myDaoConfig.setMaximumDeleteConflictQueryCount(5);
|
||||
myDeleteInterceptor.deleteConflictFunction = this::deleteConflictsFixedRetryCount;
|
||||
try {
|
||||
myOrganizationDao.delete(organizationId);
|
||||
// Needs a fourth pass to ensure that all conflicts are now gone.
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
assertEquals(DeleteConflictService.MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, e.getMessage());
|
||||
}
|
||||
|
||||
// Try again with Maximum conflict count set to 6.
|
||||
myDeleteInterceptor.myCallCount=0;
|
||||
myInterceptorDeleteCount = 0;
|
||||
myDaoConfig.setMaximumDeleteConflictQueryCount(6);
|
||||
|
||||
try {
|
||||
myOrganizationDao.delete(organizationId);
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
assertNotNull(myDeleteInterceptor.myDeleteConflictList);
|
||||
assertEquals(3, myDeleteInterceptor.myCallCount);
|
||||
assertEquals(12, myInterceptorDeleteCount);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadInterceptorNoInfiniteLoop() {
|
||||
Organization organization = new Organization();
|
||||
|
@ -150,7 +197,7 @@ public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
|
|||
myOrganizationDao.delete(organizationId);
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
assertEquals("Unable to delete Organization/" + organizationId.getIdPart() + " because at least one resource has a reference to this resource. First reference found was resource Patient/" + patientId.getIdPart() + " in path Patient.managingOrganization", e.getMessage());
|
||||
assertEquals(DeleteConflictService.MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, e.getMessage());
|
||||
}
|
||||
assertEquals(1 + DeleteConflictService.MAX_RETRY_ATTEMPTS, myDeleteInterceptor.myCallCount);
|
||||
}
|
||||
|
@ -198,6 +245,21 @@ public class DeleteConflictServiceR4Test extends BaseJpaR4Test {
|
|||
return new DeleteConflictOutcome().setShouldRetryCount(myInterceptorDeleteCount);
|
||||
}
|
||||
|
||||
private DeleteConflictOutcome deleteConflictsFixedRetryCount(DeleteConflictList theList) {
|
||||
Iterator<DeleteConflict> iterator = theList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DeleteConflict next = iterator.next();
|
||||
IdDt source = next.getSourceId();
|
||||
if ("Patient".equals(source.getResourceType())) {
|
||||
ourLog.info("Deleting {}", source);
|
||||
myPatientDao.delete(source, theList, null, null);
|
||||
// myPatientDao.delete(source);
|
||||
++myInterceptorDeleteCount;
|
||||
}
|
||||
}
|
||||
return new DeleteConflictOutcome().setShouldRetryCount(DeleteConflictService.MAX_RETRY_ATTEMPTS);
|
||||
}
|
||||
|
||||
private static class DeleteConflictInterceptor {
|
||||
int myCallCount;
|
||||
DeleteConflictList myDeleteConflictList;
|
||||
|
|
Loading…
Reference in New Issue