Search code examples
listloopssumdroolsrules

Drools iterate an object list and for all objects in the list, sum up the value of a field of the object


I am trying to build a drools rule where the fact supplied has a user supplied int value. Fact also has a list of objects that have an expected int value. I need to check if the user supplied value is less than sum of the individual objects expected value. Here is my sample fact class -

public class Order {
    private int orderId;
    private List<OrderItem> orderItems;    
    private int usersIntegrationFee;
}

The OrderItem class contained in the fact class is -

public class OrderItem {
    private int orderItemId;
    private Product prod;
}

And the Product class is the one that has productIntegrationFee field-

public class Product {
    private String mktProdId;
    private String prodName;
    private int productIntegrationFee;
}

I wish to check if Order.usersIntegrationFee is less than sum of all Order.OrderItem.Product.productIntegrationFee. Is there a way to do this in drools? Any help would be much appreciated.


Solution

  • What you need to use is a drools built-in utility called accumulate. This utility allows you to iterate over a collection of objects on the right hand side while "accumulating" some sort of variable value. Some common use cases involve sums, means, counts, and so on. Basically the construct loops over each item in a collection and performs some defined action on each item.

    While you can define your own custom accumulate function, Drools supports several built-in accumulate functions -- one of which is sum. This built-in function is what you need for your particular use case.

    The general format of accumulate is this: accumulate( <source pattern>; <functions> [;<constraints>] ). Your rule would look something like this (psuedo-code since I don't have Drools on this computer; syntax should be close or exact but typos may exist.)

    rule "Integration fee is less than sum of product fees"
    when
      // extract the variables from the Order
      Order( $userFee: usersIntegrationFee,
             $items: orderItems)
    
      // use the accumulate function to sum up the product fees
      $productFees: Integer( this > $userFee ) from 
                    accumulate( OrderItem( $product: prod ) from $items,
                                Product( $fee: productIntegrationFee ) from $product;
                                sum($fee) )
    then
      // do something
    end
    

    Some things to note. In a single accumulate, you can do multiple things -- sum, average, and so on. But if you're only invoking one accumulate function like here, you can use the syntax I've shown (Integer( conditions ) from accumulate( ... ).) If you had multiple functions, you'd have to assign the outputs directly (eg. $sum: sum($fee), etc.)

    Finally there's a third optional parameter to accumulate which I've omitted since your use case is quite simple and doesn't need it. The third parameter applies filtering (called 'constraints') so that the accumulate functions skip over items that don't meet this criteria. For example, you could add a constraint to ignore productIntegrationFee values that are negative like this: $fee > 0.

    A final note about the syntax I chose in this rule. Since the use case was "trigger the rule if the usersIntegrationFee is less than the sum," I put the comparison directly in the Integer( ... ) from accumulate. You could, of course, do a comparison separately, for example like Integer( $productFees > this ) from $userFee or whatever other format of comparison you like. This way just seemed simplest.

    The Drools documentation has more information about this utility. I've linked to the section that discusses elements in the 'when' clause; scroll down a bit to see the documentation for accumulate directly.