Search code examples
javadroolsbpmnbusiness-rules

Drools rules - multiple issues (halt execution, condition to stop execution) not working


I am using Drools 6.1.0.FINAL and a stateless session to fire all the rules.

I have below 2 simple rules in my .drl file.

rule "Null Check"
    when
        $paymentHolder : PaymentHolder(pmtRequest.requestDetails == null 
        || pmtRequest.requestDetails.paymentData == null
        || pmtRequest.requestDetails.paymentData.accountDetails == null
        || pmtRequest.requestDetails.paymentData.payments == null
        || pmtRequest.requestDetails.paymentData.payments.payment == null
        || pmtRequest.requestDetails.paymentData.payments.payment.get(0).tenderDetails == null
        || pmtRequest.requestDetails.paymentData.payments.payment.get(0).tenderDetails.paymentCardDetails == null
        || pmtRequest.requestDetails.paymentData.payments.payment.get(0).tenderDetails.paymentCardDetails.authorizationInfo == null)
    then
        System.out.println("Some Manadatory data is null ");
        // populate error code and error message
        List<ErrorHolder> errors = $paymentHolder.getErrors();
        ErrorHolder erroHolder = new ErrorHolder();
        errors.add(erroHolder);
      erroHolder.setErrorCode(com.wdpr.payment.exception.ErrorCode.MANDATORY_DATA_MISSING);
        // drools.halt();
       // kcontext.getKieRuntime().halt();        
end

rule "Amount is -ve"
    when
        
        $paymentHolder : PaymentHolder(errors.size() == 0)
    $paymentHolder(pmtRequest.getRequestDetails()!.getPaymentData().getPayments().getPayment().get(0).getAmount().getAmount() < 0)
    then
        //System.out.println(paymentHolder.getProcessId());
        System.out.println("Amount is -ve");
        //throw new com.wdpr.payment.exception.PaymentPlatformException(com.wdpr.payment.exception.ErrorCode.DATA_RANGE_INVALID, "Data Range Invalid - Amount", null);
end

I want to do this: if the first rule is true then do not execute any other rules and exit from the .drl file.

I tried following:

  1. Tried to throw a Runtime Exception inside Then block in first rule, But still it is going to second rule where it is failing because of NullPointerException.

  2. Added drools.halt() and kcontext.getKieRuntime().halt() but did not work, still going to next rule.

  3. So I added some error codes in my custom erroHolder in my first rule and checked the same condition in second rule so that it will not get executed but still it is getting executed and throwing NullPointerException.

If I remove the second rule then the first rule executes perfectly and prints the sysout. But when I run my .drl with this two rules it gives me below NPE

stacktrace

Exception in thread "main" java.lang.RuntimeException: Error while creating KieBase[Message [id=1, level=ERROR, path=com/my/payment/rules/validation.drl, line=40, column=0
   text=[ERR 102] Line 40:53 mismatched input '!.' in rule "Amount is -ve"], Message [id=2, level=ERROR, path=com/my/payment/rules/validation.drl, line=0, column=0
   text=Parser returned a null Package]]
    at org.drools.compiler.kie.builder.impl.KieContainerImpl.getKieBase(KieContainerImpl.java:349)
    at org.drools.compiler.kie.builder.impl.KieContainerImpl.newStatelessKieSession(KieContainerImpl.java:540)
    at org.drools.compiler.kie.builder.impl.KieContainerImpl.newStatelessKieSession(KieContainerImpl.java:528)
    at com.my.payment.workflow.DroolsRuleProcessor.runRules(DroolsRuleProcessor.java:58)
    at com.my.payment.workflow.DroolsRuleProcessor.main(DroolsRuleProcessor.java:81)

My java code snippet

// some code

final StatelessKieSession kSession = this.kContainer.newStatelessKieSession(rulesSession);

         // provide the necessary data and execute rules.
         kSession.execute(paymentHolder);

Note: I do not want to use salient and activate-group


Solution

  • The solution is simple (although it may not please you). To avoid an NPE in the evaluation of a constraint accessing a field containing an object, either ensure that the field is not null or combine a test against null with the access.

    // (#1) either:
    class PmtRequest {
        // guarantee non-null requestDetails
        List<rRequestDetail> requestDetails = new ArrayList<>(); 
    
    
    // (#2) or:
    when
    $paymentHolder: PaymentHolder(errors.size() == 0,
                    pmtRequest != null &&
                    pmtRequest.getRequestDetails() ... )
    then ...
    

    With a deeply nested constraint this is increasingly inconvenient, and that's why one recommends not to write such accesses but either to insert contained objects as facts or add methods in the top level object for accessing contained objects.

    There is yet another option (#3): use two sessions in your rule execution. A new PaymentHolder fact must pass the first session containing only the nun-null tests before it can be inserted into the second one.

    A solid approach would, IMHO, consist of the combination of #1 with #3.

    (At the bottom of your problem is that you need to consider the way rule evaluation works before you design your rules and the application. All constraints are evaluated when a fact is inserted. From the Drools manual: The condition evaluation is not tied to a specific evaluation sequence or point in time, but that it happens continually, at any time during the life time of the engine.)