Skip to content

Commit

Permalink
IDP-2192 ONBOARDING_KO If Drools Not Ready
Browse files Browse the repository at this point in the history
  • Loading branch information
antonio.torre committed Dec 11, 2023
1 parent 6e5a7df commit 09ddab8
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import org.kie.api.KieBase;
import reactor.core.publisher.Mono;

import java.util.Set;


/**
* This component will retrieve the beneficiaries' rules kieContainer and the PDND token associated to the input initiative id
* It will also update the cached version when new rules arrives
* */
public interface OnboardingContextHolderService {
KieBase getBeneficiaryRulesKieBase();
Set<String> getBeneficiaryRulesKieInitiativeIds();
void setBeneficiaryRulesKieBase(KieBase kieBase);

Mono<InitiativeConfig> getInitiativeConfig(String initiativeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import it.gov.pagopa.admissibility.model.InitiativeConfig;
import it.gov.pagopa.admissibility.service.build.KieContainerBuilderService;
import lombok.extern.slf4j.Slf4j;
import org.drools.core.definitions.rule.impl.RuleImpl;
import org.kie.api.KieBase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -25,10 +26,13 @@
import reactor.util.retry.Retry;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
Expand All @@ -45,6 +49,7 @@ public class OnboardingContextHolderServiceImpl extends ReadinessStateHealthIndi
private final boolean preLoadContainer;

private KieBase kieBase;
private Set<String> kieInitiatives = Collections.emptySet();
private byte[] kieBaseSerialized;

private boolean contextReady=false;
Expand Down Expand Up @@ -89,8 +94,7 @@ public OnboardingContextHolderReadyEvent(Object source) {
//region kieContainer holder
@Override
public void setBeneficiaryRulesKieBase(KieBase newKieBase) {
preLoadKieBase(newKieBase);
this.kieBase = newKieBase;
acceptNewKieBase(newKieBase);

if (isRedisCacheEnabled) {
kieBaseSerialized = SerializationUtils.serialize(newKieBase);
Expand All @@ -102,6 +106,23 @@ public void setBeneficiaryRulesKieBase(KieBase newKieBase) {
}
}

private void acceptNewKieBase(KieBase newKieBase) {
preLoadKieBase(newKieBase);
this.kieBase = newKieBase;
this.kieInitiatives = readKieInitiatives(newKieBase);
}

private Set<String> readKieInitiatives(KieBase kieBase) {
if (kieBase == null) {
return Collections.emptySet();
} else {
return kieBase.getKiePackages().stream()
.flatMap(p -> p.getRules().stream())
.map(r -> ((RuleImpl) r).getAgendaGroup())
.collect(Collectors.toSet());
}
}

private void preLoadKieBase(KieBase kieBase) {
if (preLoadContainer) {
kieContainerBuilderService.preLoadKieBase(kieBase);
Expand All @@ -113,6 +134,11 @@ public KieBase getBeneficiaryRulesKieBase() {
return kieBase;
}

@Override
public Set<String> getBeneficiaryRulesKieInitiativeIds() {
return kieInitiatives;
}

@Scheduled(initialDelayString = "${app.beneficiary-rule.cache.refresh-ms-rate}", fixedRateString = "${app.beneficiary-rule.cache.refresh-ms-rate}")
public void refreshKieContainer() {
refreshKieContainer(x -> log.trace("Refreshed KieContainer"), Retry.max(3), Mono::error);
Expand All @@ -129,9 +155,7 @@ public void refreshKieContainer(
this.kieBaseSerialized = c;
try {
KieBase newKieBase = org.apache.commons.lang3.SerializationUtils.deserialize(c);
preLoadKieBase(newKieBase);

this.kieBase = newKieBase;
acceptNewKieBase(newKieBase);
} catch (Exception e) {
log.warn("[BENEFICIARY_RULE_BUILDER] Cached KieContainer cannot be executed! refreshing it!");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
import it.gov.pagopa.admissibility.dto.onboarding.EvaluationDTO;
import it.gov.pagopa.admissibility.dto.onboarding.OnboardingDTO;
import it.gov.pagopa.admissibility.dto.onboarding.OnboardingDroolsDTO;
import it.gov.pagopa.admissibility.dto.onboarding.OnboardingRejectionReason;
import it.gov.pagopa.admissibility.mapper.Onboarding2EvaluationMapper;
import it.gov.pagopa.admissibility.mapper.Onboarding2OnboardingDroolsMapper;
import it.gov.pagopa.admissibility.model.InitiativeConfig;
import it.gov.pagopa.admissibility.service.CriteriaCodeService;
import it.gov.pagopa.admissibility.service.onboarding.OnboardingContextHolderService;
import it.gov.pagopa.admissibility.utils.OnboardingConstants;
import it.gov.pagopa.common.reactive.utils.PerformanceLogger;
import lombok.extern.slf4j.Slf4j;
import org.drools.core.command.runtime.rule.AgendaGroupSetFocusCommand;
import org.kie.api.command.Command;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.internal.command.CommandFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -52,10 +55,26 @@ public EvaluationDTO applyRules(OnboardingDTO onboardingRequest, InitiativeConfi
long before = System.currentTimeMillis();
statelessKieSession.execute(CommandFactory.newBatchExecution(cmds));

checkIfContainerWasReady(req, initiative);

PerformanceLogger.logTiming("ONBOARDING_RULE_ENGINE", before, "resulted into rejections %s".formatted(req.getOnboardingRejectionReasons()));

log.trace("[ONBOARDING_REQUEST] [RULE_ENGINE] Send message prepared: {}", req);

return onboarding2EvaluationMapper.apply(req, initiative, req.getOnboardingRejectionReasons());
}

private void checkIfContainerWasReady(OnboardingDroolsDTO req, InitiativeConfig initiative) {
if (req.getOnboardingRejectionReasons().isEmpty() && // there is not rejection reason
!CollectionUtils.isEmpty(initiative.getAutomatedCriteria()) && // the drools container is supposed to be involved
!onboardingContextHolderService.getBeneficiaryRulesKieInitiativeIds() // the initiative was not inside the container drools
.contains(initiative.getInitiativeId())
) {
req.getOnboardingRejectionReasons().add(new OnboardingRejectionReason(
OnboardingRejectionReason.OnboardingRejectionReasonType.TECHNICAL_ERROR,
OnboardingConstants.REJECTION_REASON_RULE_ENGINE_NOT_READY,
null, null, null
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private OnboardingConstants(){}
public static final String REJECTION_REASON_FAMILY_KO = "FAMILY_FAIL";
public static final String REJECTION_REASON_RESIDENCE_KO = "RESIDENCE_FAIL";
public static final String REJECTION_REASON_BIRTHDATE_KO = "BIRTHDATE_FAIL";
public static final String REJECTION_REASON_RULE_ENGINE_NOT_READY = "RULE_ENGINE_NOT_READY";
public static final String REJECTION_REASON_GENERIC_ERROR = "GENERIC_ERROR";

/** Rejection reason for the second family member due to the family not meeting the requirements of the initiative */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void testBeneficiaryRuleBuilding() {
long timeEnd = System.currentTimeMillis();

Assertions.assertEquals(validRules, countSaved[0]);
Assertions.assertEquals(validRules, onboardingContextHolderServiceSpy.getBeneficiaryRulesKieInitiativeIds().size());
Assertions.assertEquals(expectedRules[0], ruleBuiltSize);

checkInitiativeCounters(validRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/*
******************
Expand Down Expand Up @@ -144,6 +145,7 @@ private void checkResult(DroolsRule result, Initiative2BuildDTO dto) {
@Test
void testExecutions() {
testExecution(Collections.emptyList());
testExecution(List.of("NOTREADY"));
testExecution(List.of("ISEE"));
testExecution(List.of("BIRTHDATE"));
testExecution(List.of("ISEE", "BIRTHDATE"));
Expand All @@ -153,6 +155,7 @@ void testExecution(List<String> failingCode) {
//given
boolean expectedIseeFail = failingCode.contains("ISEE");
boolean expectedBirthDateFail = failingCode.contains("BIRTHDATE");
boolean expectedNotReady = failingCode.equals(List.of("NOTREADY"));

Initiative2BuildDTO initiative = buildInitiative();

Expand All @@ -175,6 +178,10 @@ void testExecution(List<String> failingCode) {

OnboardingContextHolderService onboardingContextHolderService = Mockito.mock(OnboardingContextHolderService.class);
Mockito.when(onboardingContextHolderService.getBeneficiaryRulesKieBase()).thenReturn(buildKieBase(rule));
Mockito.when(onboardingContextHolderService.getBeneficiaryRulesKieInitiativeIds())
.thenReturn(expectedNotReady
? Collections.emptySet()
: Set.of(initiative.getInitiativeId()));

RuleEngineService ruleEngineService = new RuleEngineServiceImpl(onboardingContextHolderService, new Onboarding2EvaluationMapper(), criteriaCodeServiceMock, new Onboarding2OnboardingDroolsMapper());

Expand Down Expand Up @@ -208,7 +215,13 @@ void testExecution(List<String> failingCode) {
.authorityLabel("Agenzia per l'Italia Digitale")
.build());
}
expectedEvaluationResult.setStatus(expectedEvaluationResult.getOnboardingRejectionReasons().size() == 0 ? OnboardingEvaluationStatus.ONBOARDING_OK : OnboardingEvaluationStatus.ONBOARDING_KO);
if (expectedNotReady) {
expectedEvaluationResult.getOnboardingRejectionReasons().add(OnboardingRejectionReason.builder()
.type(OnboardingRejectionReason.OnboardingRejectionReasonType.TECHNICAL_ERROR)
.code("RULE_ENGINE_NOT_READY")
.build());
}
expectedEvaluationResult.setStatus(expectedEvaluationResult.getOnboardingRejectionReasons().isEmpty() ? OnboardingEvaluationStatus.ONBOARDING_OK : OnboardingEvaluationStatus.ONBOARDING_KO);

Assertions.assertEquals(expectedEvaluationResult, evaluationResult);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -78,6 +79,7 @@ void getKieContainer(boolean isRedisCacheEnabled) {

//Then
Assertions.assertNotNull(result);
Assertions.assertEquals(Collections.emptySet(), onboardingContextHolderService.getBeneficiaryRulesKieInitiativeIds());
if (!isRedisCacheEnabled) {
Assertions.assertSame(expectedKieBase, result);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,84 @@
package it.gov.pagopa.admissibility.service.onboarding.evaluate;

import it.gov.pagopa.admissibility.connector.repository.DroolsRuleRepository;
import it.gov.pagopa.admissibility.drools.transformer.extra_filter.ExtraFilter2DroolsTransformerFacadeImplTest;
import it.gov.pagopa.admissibility.dto.onboarding.*;
import it.gov.pagopa.admissibility.dto.rule.AutomatedCriteriaDTO;
import it.gov.pagopa.admissibility.enums.OnboardingEvaluationStatus;
import it.gov.pagopa.admissibility.mapper.Onboarding2EvaluationMapper;
import it.gov.pagopa.admissibility.mapper.Onboarding2OnboardingDroolsMapper;
import it.gov.pagopa.admissibility.model.DroolsRule;
import it.gov.pagopa.admissibility.model.InitiativeConfig;
import it.gov.pagopa.admissibility.connector.repository.DroolsRuleRepository;
import it.gov.pagopa.admissibility.service.CriteriaCodeService;
import it.gov.pagopa.admissibility.service.build.KieContainerBuilderServiceImpl;
import it.gov.pagopa.admissibility.service.build.KieContainerBuilderServiceImplTest;
import it.gov.pagopa.admissibility.service.onboarding.OnboardingContextHolderService;
import it.gov.pagopa.admissibility.service.onboarding.OnboardingContextHolderServiceImpl;
import it.gov.pagopa.admissibility.utils.OnboardingConstants;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.kie.api.KieBase;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

@ExtendWith(MockitoExtension.class)
@Slf4j
class RuleEngineServiceImplTest {

public static final String INITIATIVEID = "INITIATIVEID";
@Mock private OnboardingContextHolderService onboardingContextHolderServiceMock;
@Mock private CriteriaCodeService criteriaCodeServiceMock;

private final Onboarding2EvaluationMapper onboarding2EvaluationMapper = new Onboarding2EvaluationMapper();
private final Onboarding2OnboardingDroolsMapper onboarding2OnboardingDroolsMapper = new Onboarding2OnboardingDroolsMapper();

private RuleEngineService ruleEngineService;

@BeforeAll
public static void configDroolsLogLevel() {
KieContainerBuilderServiceImplTest.configDroolsLogs();
}

@BeforeEach
void init(){
ruleEngineService = new RuleEngineServiceImpl(onboardingContextHolderServiceMock, onboarding2EvaluationMapper, criteriaCodeServiceMock, onboarding2OnboardingDroolsMapper);
}

@AfterEach
void verifyNotMoreInvocations(){
Mockito.verifyNoMoreInteractions(
onboardingContextHolderServiceMock,
criteriaCodeServiceMock);
}

@Test
void applyRules() {
// Given
OnboardingContextHolderService onboardingContextHolderServiceMock = Mockito.mock(OnboardingContextHolderServiceImpl.class);
CriteriaCodeService criteriaCodeServiceMock = Mockito.mock(CriteriaCodeService.class);
Onboarding2EvaluationMapper onboarding2EvaluationMapper = new Onboarding2EvaluationMapper();
Onboarding2OnboardingDroolsMapper onboarding2OnboardingDroolsMapper = new Onboarding2OnboardingDroolsMapper();

RuleEngineService ruleEngineService = new RuleEngineServiceImpl(onboardingContextHolderServiceMock, onboarding2EvaluationMapper, criteriaCodeServiceMock, onboarding2OnboardingDroolsMapper);

String initiativeId = INITIATIVEID;
OnboardingDTO onboardingDTO = new OnboardingDTO();
onboardingDTO.setInitiativeId("INITIATIVEID");
onboardingDTO.setInitiativeId(initiativeId);

InitiativeConfig initiativeConfig = new InitiativeConfig();
initiativeConfig.setInitiativeId("INITIATIVEID");
initiativeConfig.setInitiativeId(initiativeId);
initiativeConfig.setInitiativeName("INITIATIVENAME");
initiativeConfig.setOrganizationId("ORGANIZATIONID");

Mockito.when(onboardingContextHolderServiceMock.getBeneficiaryRulesKieBase()).thenReturn(buildContainer(onboardingDTO.getInitiativeId()));
Mockito.when(onboardingContextHolderServiceMock.getBeneficiaryRulesKieBase())
.thenReturn(buildContainer(onboardingDTO.getInitiativeId()));

// When
EvaluationDTO result = ruleEngineService.applyRules(onboardingDTO, initiativeConfig);

// Then
Mockito.verify(onboardingContextHolderServiceMock).getBeneficiaryRulesKieBase();

Assertions.assertTrue(result instanceof EvaluationCompletedDTO);
Assertions.assertInstanceOf(EvaluationCompletedDTO.class, result);
Assertions.assertNotNull(result.getAdmissibilityCheckDate());
Assertions.assertFalse(result.getAdmissibilityCheckDate().isAfter(LocalDateTime.now()));
Assertions.assertTrue(result.getAdmissibilityCheckDate().isAfter(LocalDateTime.now().minusMinutes(2)));
Expand All @@ -79,6 +96,58 @@ void applyRules() {
Assertions.assertEquals(expected, result);
}

@Test
void testNotRules_notKieBasedInitiatives(){
// Given
String initiativeId = "NOTKIEINITITATIVEID";
OnboardingDTO onboardingDTO = new OnboardingDTO();
onboardingDTO.setInitiativeId(initiativeId);

InitiativeConfig initiativeConfig = new InitiativeConfig();
initiativeConfig.setInitiativeId(initiativeId);

Mockito.when(onboardingContextHolderServiceMock.getBeneficiaryRulesKieBase())
.thenReturn(buildContainer(INITIATIVEID));

// When
EvaluationDTO result = ruleEngineService.applyRules(onboardingDTO, initiativeConfig);

// Then
Assertions.assertInstanceOf(EvaluationCompletedDTO.class, result);
Assertions.assertEquals(OnboardingEvaluationStatus.ONBOARDING_OK, ((EvaluationCompletedDTO)result).getStatus());
Assertions.assertEquals(Collections.emptyList(), ((EvaluationCompletedDTO)result).getOnboardingRejectionReasons());
}

@Test
void testNotRules_kieBasedInitiatives(){
// Given
String initiativeId = "KIEINITITATIVEID_NOT_IN_CONTAINER";
OnboardingDTO onboardingDTO = new OnboardingDTO();
onboardingDTO.setInitiativeId(initiativeId);

InitiativeConfig initiativeConfig = new InitiativeConfig();
initiativeConfig.setInitiativeId(initiativeId);
initiativeConfig.setAutomatedCriteria(List.of(AutomatedCriteriaDTO.builder().build()));

Mockito.when(onboardingContextHolderServiceMock.getBeneficiaryRulesKieBase())
.thenReturn(buildContainer(INITIATIVEID));
Mockito.when(onboardingContextHolderServiceMock.getBeneficiaryRulesKieInitiativeIds())
.thenReturn(Collections.emptySet());

// When
EvaluationDTO result = ruleEngineService.applyRules(onboardingDTO, initiativeConfig);

// Then
Assertions.assertInstanceOf(EvaluationCompletedDTO.class, result);
Assertions.assertEquals(OnboardingEvaluationStatus.ONBOARDING_KO, ((EvaluationCompletedDTO)result).getStatus());
Assertions.assertEquals(List.of(new OnboardingRejectionReason(
OnboardingRejectionReason.OnboardingRejectionReasonType.TECHNICAL_ERROR,
OnboardingConstants.REJECTION_REASON_RULE_ENGINE_NOT_READY,
null, null, null
)),
((EvaluationCompletedDTO)result).getOnboardingRejectionReasons());
}

private KieBase buildContainer(String initiativeId) {
DroolsRule ignoredRule = new DroolsRule();
ignoredRule.setId("IGNORED");
Expand Down

0 comments on commit 09ddab8

Please sign in to comment.