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:


    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.