droolsbusiness-ruleskiedrools-flow

Drools check if one property from list is present in other list


I have got followed structure:

public class Proposal {
   private final List<Product> products; 
   private final List<Customer> customers;
}

public class Customer {
   private String id;
   private String country;
}

public class Product {
  private String customerId;
  private String country;
  private String type; 
}

the requirements are: Don't check if the product type is "A" or "B" and for customer with id: 21. For any other products check if customerId match with id or if the customer country is set to null and the country on the product is set to 'US'

Based on that I prepared a rule but it does not work as it should:

//rule body
Proposal($products: products, $customers: customers)

$productsToChecks : Product(customerId != "21") && type not in ("A", "B") from $products

//first condition to check if there is any applicable case:
exist(Product(customerId != "21") && type not in ("A", "B") from $products) and

(
  // check if there is a customer who can use this product
  forall (
    Product($customerId: customerId, $country: country) from $productsToChecks
    Customer(id == $customerId || (country == null &&  $country == "US"))

  )
)

Thank you for any help or advice


Solution

  • You have a syntax error in your $productsToChecks declaration:

    $productsToChecks : Product(customerId != "21") && type not in ("A", "B") from $products
    

    Both of the attributes you're checking need to be inside of the Product( ... ) part like this:

    $productsToChecks: Product( customerId != "21",
                                type not in ("A", "B")) from $products
    

    You repeat this error in other parts of the rule as well.


    So your requirements are:

    Don't check if the product type is "A" or "B" and for customer with id: 21. For any other products check if customerId match with id or if the customer country is set to null and the country on the product is set to 'US'

    We can distill this to the following pseudo-code:

    Given the 'OR' in the second part, this is two rules.

    The first part we need to do is find the subset of products that we care about. You can do this in a number of ways -- collect or accumulate are the two that immediately come to mind. Assuming that the requirements in your question are complete, collect is more appropriate here (and simpler).

    Proposal($products: products, $customers: customers)
    
    $productSubset: List() from collect( Product( customerId != 21, type not in ("A", "B") ) from $products)
    

    Now you can use that subset of products (which don't include the ones you need to ignore) to match your other criteria. As I mentioned, since those criteria are OR'd, they should be two distinct rules.

    rule "Product customerId matches Customer id"
    when
      Proposal($products: products, $customers: customers)
      $productSubset: List() 
                      from collect( Product( customerId != 21, type not in ("A", "B") ) from $products)
    
      Customer( $id: id != null ) from $customers
      $product: Product( customerId == $id ) from $productSubset
    then
      // do something with $product
    end
    
    rule "US Product and no Customer Country"
    when
      Proposal($products: products, $customers: customers)
      $productSubset: List() 
                      from collect( Product( customerId != 21, type not in ("A", "B") ) from $products)
    
      Customer( country == null ) from $customers
      $product: Product( country == "US" ) from $productSubset
    then
      // do something with $product
    end
    

    To cut down on the duplicate code, you can pull the common conditions into a single 'parent' rule and then use the extends keyword to create the two child rules with their distinct conditions.

    I designed these rules in this way under the assumption that you want to do some action against each of the products that meets your criteria. Based on this assumption, the right hand side will trigger for each product that matches the criteria of each rule (also note that since the two rules are not exclusive, products might trigger twice if the customerId matches and the country requirements are satisfied.)

    However if all you want as the result is a list of all products that meets the criteria, you can again use a function to obtain that list of products. In this case, the accumulate function more appropriate than collect:

    rule "Get list of products for customer"
    when
      Proposal($products: products, $customers: customers)
      $productSubset: List() 
                      from collect( Product( customerId != 21, type not in ("A", "B") ) from $products)
    
      Customer( $id: id != null, $country: country ) from $customers
    
      $product: Product( customerId == $id ) from $productSubset
    
      $customerProducts: List() from accumulate(
                           $p: Product((customerId == $id) || ($country == null && country == "US")) from $products,
                           collectList($p)
                         )
    then
      // do something with $customerProducts
    end