Search code examples
drools

How to find out exists one element and use it in Drools?


Is there any way to use exists with a variable $p to refer it and use it in action?

I want to check if exists an object in my Working Memory, with exists to avoid the rule match more than once, but I want to save the reference to the object matched so I could use it in action.

Example what I want:

package test;
unit TestUnit;

rule "test_rule"
when
    exists /test/cityList[$id : id,name=="Washington"]
then
    System.out.println("there is one result id"+$id);
end

rule unit:

public class TestUnit implements RuleUnitData {
    private final DataStore<TestRule> test;
    public TestUnit() {
        this(DataSource.createStore());
    }
    public TestUnit(DataStore<TestRule> test) {
        this.test = test;
    }
}

POJO:

public class TestRule {
    private Long id;
    private List<City> cityList;
}

public class City {
    private Long id;
    private String name;
}

Any idea or alternative to do it?

I tried run the code:

package test;
unit TestUnit;

rule "test_rule"
when
    exists /test/cityList[$id : id,name=="Washington"]
then
    System.out.println("there is one result id"+$id);
end

but results : text=$id cannot be resolved to a variable

then I tried in another way:

package test;
unit TestUnit;

rule "test_rule"
when
    /test/cityList[$id : id]
    exists /test/cityList[name=="Washington"]
then
    System.out.println("there is one result id"+$id);
end

but it execute more then one times


Solution

  • Is there any way to use exists with a variable $p to refer it and use it in action?

    No, because exists tests that there exists at least one record that matches your condition. If there are three, which would you expect to be assigned to $p?

    (That's a rhetorical question, because it would be indeterminate. There is no guaranteed order to how Drools checks members in a collection.)

    I'll go over your options in more detail below, but in general:

    • You use exists when you want to test that there is at least one record in working memory that meets your conditions, but you're not particularly interested in the details.
      • For example, if you have a set of rules for alerting if there's a validation failure, you could have a rule that activates that group of notification rules if there's at least one ValidationFailure object present. If there are no ValidationFailure objects present, there's no need to waste time, memory, and CPU in activating those rules.
      • There's a not predicate that's the negative analog of exists when you want to verify there is nothing in working memory that meets your conditions.
    • If you want to trigger every time you have a match, you just do your conditional normally (without exists). So if there are 3 cities called Washington, the rule will trigger three times, and $p will be assigned to separate city when its their turn.
    • If you only want to trigger when there's exactly one match, you'll want to do a collect and verify the length of your result. If there is exactly one city Washington, your rule will trigger and you can get a direct reference to that city. If there is more than one city, the rule will not trigger at all.

    As you've discovered, you can only use the exists predicate if you want to determine presence of a matching record.

    (I'm going to use the slightly older syntax with parentheses in my example instead of the newer one with slashes; the concepts are the same.)

    rule "Fire exactly once if there is at least one city called 'Washington'"
    when
      exists( City( name == "Washington" ) from cityList )
    then
      System.out.println("There exists at least one city Washington.")
    end
    

    However, you do not have a reference to that City instance, so you can't print its ID. After all, what if you had two cities called Washington? Which would you expect to match? It would be indeterminate. The point of exists is only to test for existence.


    If you want to trigger for every city called Washington, the solution is to simply remove exists.

    rule "Fire for each city called 'Washington'"
    when
      City( $id: id, name == "Washington" ) from cityList
    then
      System.out.println("Found Washington with id " + $id)
    end
    

    If you had three cities called Washington in your list, this would fire and print 3 times, once for each ID.


    If you want your rule to fire if there is only exactly one city called Washington, you'll need to collect all the qualifying cities and verify that there is exactly one item in that list.

    rule "Fire if there is only exactly 1 city called 'Washington'"
    when
      List( size == 1 ) from collect( City( name == "Washington" ) from cityList)
    then
      System.out.println("There is exactly one city called Washington")
    end
    

    In this case, you end up with a List, so you can easily assign a reference to that, pull the City out, and reference its id and other values.