Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinYSpasov committed Dec 5, 2024
1 parent 9b8a16d commit 3e5da7d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 57 deletions.
88 changes: 45 additions & 43 deletions src/main/java/uk/gov/hmcts/reform/ccd/ApplicationExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
import uk.gov.hmcts.reform.ccd.service.CaseFinderService;
import uk.gov.hmcts.reform.ccd.util.log.CaseFamiliesFilter;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static uk.gov.hmcts.reform.ccd.util.CaseFamilyUtil.FLATTEN_CASE_FAMILIES_FUNCTION;
import static uk.gov.hmcts.reform.ccd.util.CaseFamilyUtil.POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION;
Expand All @@ -31,65 +34,64 @@ public void execute() {
log.info("Case-Disposer started...");
final List<CaseFamily> caseFamiliesDueDeletion = caseFindingService.findCasesDueDeletion();
final List<CaseFamily> deletableCasesOnly = caseFamiliesFilter.getDeletableCasesOnly(caseFamiliesDueDeletion);
final List<CaseFamily> deletableLinkedFamiliesSimulation = caseFamiliesFilter
.geSimulationCasesOnly(caseFamiliesDueDeletion);

final List<CaseFamily> actuallyDeletableCases = new ArrayList<>();
final List<CaseFamily> deletableLinkedFamiliesSimulation = caseFamiliesFilter.geSimulationCasesOnly(caseFamiliesDueDeletion);

final Set<Long> processedFamilyIds = ConcurrentHashMap.newKeySet();
final List<CaseData> flattenedCaseFamiliesView = FLATTEN_CASE_FAMILIES_FUNCTION.apply(deletableCasesOnly);

final List<CaseData> potentialMultiFamilyCases =
POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(deletableCasesOnly);
AtomicInteger requestLimit = new AtomicInteger(parameterResolver.getRequestLimit());

Integer requestLimit = parameterResolver.getRequestLimit();
final List<List<CaseData>> splitCaseFamily = POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(deletableCasesOnly);

for (CaseData subjectCaseData : potentialMultiFamilyCases) {
final List<CaseFamily> linkedFamilies = findLinkedCaseFamilies(
flattenedCaseFamiliesView,
caseFamiliesDueDeletion,
subjectCaseData
);
final int linkedFamilySize = FLATTEN_CASE_FAMILIES_FUNCTION.apply(linkedFamilies).size();
splitCaseFamily.parallelStream().takeWhile(caseDataList -> requestLimit.get() > 0).forEach(caseDataList -> {
final List<CaseFamily> deletableCaseFamilies = getCaseFamilies(caseDataList, flattenedCaseFamiliesView, caseFamiliesDueDeletion);

// If a root case has more than 1 child case, the linked family may be processed more than once.
// To avoid this, we check if the family id of the root case has already been processed.
final boolean hasNotBeenProcessed = actuallyDeletableCases
.stream().noneMatch(caseFamily -> caseFamily.getRootCase().getFamilyId()
.equals(subjectCaseData.getFamilyId()));

// The RequestLimit specifies the total number of cases that can be deleted.
// In some scenarios, the linkedFamilySize may exceed the requestLimit, causing the deletion to be skipped.
// However, in other scenarios, if the linkedFamilySize is less than or equal to the requestLimit,
// the deletion will proceed.
if (hasNotBeenProcessed && (requestLimit >= linkedFamilySize)) {
caseDeletionService.deleteLinkedCaseFamilies(linkedFamilies);
actuallyDeletableCases.addAll(linkedFamilies);
requestLimit -= linkedFamilySize;
final boolean hasNotBeenProcessed = deletableCaseFamilies.stream()
.map(caseFamily -> caseFamily.getRootCase().getFamilyId())
.noneMatch(processedFamilyIds::contains);

if (hasNotBeenProcessed) {
final int linkedFamilySize = FLATTEN_CASE_FAMILIES_FUNCTION.apply(deletableCaseFamilies).size();

// The RequestLimit defines the maximum number of cases that can be deleted.
if (requestLimit.get() >= linkedFamilySize) {
caseDeletionService.deleteLinkedCaseFamilies(deletableCaseFamilies);
processedFamilyIds.addAll(deletableCaseFamilies.stream()
.map(caseFamily -> caseFamily.getRootCase().getFamilyId())
.collect(Collectors.toSet()));
requestLimit.addAndGet(-linkedFamilySize);
}
}
}
});

caseDeletionResolver.logCaseDeletion(actuallyDeletableCases, deletableLinkedFamiliesSimulation);
caseDeletionResolver.logCaseDeletion(deletableCasesOnly, deletableLinkedFamiliesSimulation);
log.info("Case-Disposer finished.");
}

private List<CaseFamily> findLinkedCaseFamilies(final List<CaseData> flattenedCasesView,
final List<CaseFamily> allCaseFamilies,
final CaseData subjectCaseData) {
final List<Long> linkedFamilyIds = buildLinkedFamilyIds(flattenedCasesView, subjectCaseData);
return buildLinkedFamilies(allCaseFamilies, linkedFamilyIds);
private List<CaseFamily> getCaseFamilies(final List<CaseData> caseDataList,
final List<CaseData> flattenedCaseFamiliesView,
final List<CaseFamily> caseFamiliesDueDeletion) {
// Collect the family IDs of matching case data
final Set<Long> matchingFamilyIds = getMatchingFamilyIds(caseDataList, flattenedCaseFamiliesView);

// Filter case families due for deletion that have matching family IDs
return getMatchingCaseFamilies(caseFamiliesDueDeletion, matchingFamilyIds);
}

private List<Long> buildLinkedFamilyIds(final List<CaseData> flattenedCasesView, final CaseData candidateCaseData) {
return flattenedCasesView.stream()
.filter(caseData -> caseData.getId().equals(candidateCaseData.getId()))
.map(CaseData::getFamilyId)
.toList();
private Set<Long> getMatchingFamilyIds(List<CaseData> caseDataList, List<CaseData> flattenedCaseFamiliesView) {
return caseDataList.parallelStream()
.flatMap(caseData -> flattenedCaseFamiliesView.parallelStream()
.filter(flattenedCase -> flattenedCase.getId().equals(caseData.getId()))
.map(CaseData::getFamilyId))
.collect(Collectors.toSet());
}

private List<CaseFamily> buildLinkedFamilies(final List<CaseFamily> allCaseFamilies,
final List<Long> linkedCaseFamilyIds) {
return allCaseFamilies.stream()
.filter(caseFamily -> linkedCaseFamilyIds.contains(caseFamily.getRootCase().getFamilyId()))
private List<CaseFamily> getMatchingCaseFamilies(final List<CaseFamily> caseFamiliesDueDeletion,
final Set<Long> matchingFamilyIds) {
return caseFamiliesDueDeletion.parallelStream()
.filter(caseFamily -> matchingFamilyIds.contains(caseFamily.getRootCase().getFamilyId()))
.toList();
}
}
20 changes: 11 additions & 9 deletions src/main/java/uk/gov/hmcts/reform/ccd/util/CaseFamilyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import uk.gov.hmcts.reform.ccd.data.model.CaseData;
import uk.gov.hmcts.reform.ccd.data.model.CaseFamily;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

Expand All @@ -25,14 +23,18 @@ private CaseFamilyUtil() {
* Duplicate cases may occur when a case belongs to multiple root cases. This ensures
* that each case is processed only once, avoiding issues like deleting the same case family multiple times.
*/
public static final Function<List<CaseFamily>, List<CaseData>> POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION =
caseFamilies -> {
Set<Long> seenIds = new HashSet<>();
public static final Function<List<CaseFamily>, List<List<CaseData>>>
POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION = caseFamilies -> {
return caseFamilies.stream()
.flatMap(caseFamily ->
caseFamily.getLinkedCases().isEmpty()
? Stream.of(caseFamily.getRootCase()) : caseFamily.getLinkedCases().stream())
.filter(caseData -> seenIds.add(caseData.getId()))
.map(caseFamily -> caseFamily.getLinkedCases().isEmpty()
? List.of(caseFamily.getRootCase()) // Wrap the root case in a list
: caseFamily.getLinkedCases()) // Return linked cases as a list
.toList();
};
}






Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA03_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA05_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA09_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA10_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA11_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA4_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.DELETABLE_CASE_DATA_WITH_PAST_TTL;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.linkedCasesFamilyId_1;
import static uk.gov.hmcts.reform.ccd.fixture.TestData.linkedCasesFamilyId_4;

@ExtendWith(MockitoExtension.class)
class ApplicationExecutorTest {
Expand Down Expand Up @@ -108,7 +111,7 @@ void shouldLimitCaseDeletionToRequestsLimit() {
@Test
void shouldDeleteAllCaseDataIfRequestsLimitGreaterThanSize() {
// Given
when(parameterResolver.getRequestLimit()).thenReturn(10);
when(parameterResolver.getRequestLimit()).thenReturn(3);

final List<CaseFamily> caseDataList = List.of(
new CaseFamily(DELETABLE_CASE_DATA_WITH_PAST_TTL, emptyList()),
Expand Down Expand Up @@ -154,4 +157,31 @@ void shouldLimitCaseDeletionToRequestsLimitBasedOnLinkedCases() {
verify(caseDeletionService, times(2)).deleteLinkedCaseFamilies(anyList());
verify(caseDeletionResolver, times(1)).logCaseDeletion(anyList(),anyList());
}

@Test
void shouldNotDeleteCaseDataIfRequestsLimitLessThanAllLinkedCases() {
// Given
when(parameterResolver.getRequestLimit()).thenReturn(5);

// We will delete a single case only (DELETABLE_CASE_DATA05_WITH_PAST_TTL) because
// the request limit is 5 and the linked cases are 6
final CaseFamily caseFamily3 = new CaseFamily(DELETABLE_CASE_DATA05_WITH_PAST_TTL, emptyList());
final List<CaseFamily> caseDataList = List.of(
new CaseFamily(DELETABLE_CASE_DATA_WITH_PAST_TTL, linkedCasesFamilyId_1),
new CaseFamily(DELETABLE_CASE_DATA4_WITH_PAST_TTL, linkedCasesFamilyId_4),
caseFamily3
);
doReturn(caseDataList)
.when(caseFindingService).findCasesDueDeletion();
doReturn(caseDataList)
.when(caseFamiliesFilter).getDeletableCasesOnly(caseDataList);
doNothing().when(caseDeletionResolver).logCaseDeletion(anyList(),anyList());

applicationExecutor.execute();

verify(caseFindingService).findCasesDueDeletion();
verify(caseDeletionService, times(1)).deleteLinkedCaseFamilies(List.of(caseFamily3));
verify(caseDeletionResolver, times(1)).logCaseDeletion(anyList(),anyList());
}

}
18 changes: 18 additions & 0 deletions src/test/java/uk/gov/hmcts/reform/ccd/fixture/TestData.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,22 @@ public interface TestData {
TODAY,
2L,
null);

CaseData DELETABLE_CASE_DATA80_WITH_PAST_TTL = new CaseData(80L, 80L, DELETABLE_CASE_TYPE,
JURISDICTION, YESTERDAY, 1L, null);
CaseData DELETABLE_CASE_DATA81_WITH_PAST_TTL = new CaseData(81L, 81L, DELETABLE_CASE_TYPE,
JURISDICTION, YESTERDAY, 1L, null);

CaseData DELETABLE_CASE_DATA81_DUPLICATE_WITH_PAST_TTL = new CaseData(81L, 81L, DELETABLE_CASE_TYPE,
JURISDICTION, YESTERDAY, 4L, null);
CaseData DELETABLE_CASE_DATA82_WITH_PAST_TTL = new CaseData(82L, 82L, DELETABLE_CASE_TYPE,
JURISDICTION, YESTERDAY, 4L, null);

CaseData DELETABLE_CASE_DATA83_WITH_PAST_TTL = new CaseData(83L, 83L, DELETABLE_CASE_TYPE,
JURISDICTION, YESTERDAY, 6L, null);

List<CaseData> linkedCasesFamilyId_1 = List.of(DELETABLE_CASE_DATA80_WITH_PAST_TTL,
DELETABLE_CASE_DATA81_WITH_PAST_TTL);
List<CaseData> linkedCasesFamilyId_4 = List.of(DELETABLE_CASE_DATA81_DUPLICATE_WITH_PAST_TTL,
DELETABLE_CASE_DATA82_WITH_PAST_TTL);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ void testShouldFlattenCaseFamilies() {
void testShouldReturnOnlyRootCaseWhenNoLinkedCasesPresent() {
final List<CaseFamily> caseFamilies = List.of(new CaseFamily(caseData1, emptyList()));

final List<CaseData> result = POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(caseFamilies);
final List<List<CaseData>> result = POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(caseFamilies);

assertThat(result)
assertThat(result.getFirst())
.singleElement()
.isEqualTo(caseData1);
}
Expand All @@ -59,9 +59,9 @@ void testShouldReturnOnlyRootCaseWhenNoLinkedCasesPresent() {
void testResultShouldNotContainRootCaseWhenLinkedCasesPresent() {
final List<CaseFamily> caseFamilies = List.of(new CaseFamily(caseData1, List.of(caseData2)));

final List<CaseData> result = POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(caseFamilies);
final List<List<CaseData>> result = POTENTIAL_MULTI_FAMILY_CASE_AGGREGATOR_FUNCTION.apply(caseFamilies);

assertThat(result)
assertThat(result.getFirst())
.singleElement()
.isEqualTo(caseData2);
}
Expand Down

0 comments on commit 3e5da7d

Please sign in to comment.