I'm running into a bizarre situation where a Fact that has been deleted is getting matched by a rule downstream. This seems to only happen when these conditions are met in this exact order:
- Multiple logically unequal Facts (via the
equals()
method) are inserted into WM. - A rule updates the properties of one of those Facts such that it is now logically equivalent to an existing Fact in WM.
- A different rule deletes the Fact that was updated in step 2).
- A different rule matches on the Fact that was deleted in step 3) via
from collect
. org.drools.FactException: Update error: handle not found for object
is thrown when the rule in step 4) tries to update or retract the previously deleted Fact.
An important thing to note about step 4): this "Ghost Fact" is only matched in a from collect
. If a rule at this stage is just a single pattern, it will not match the Fact that was previously deleted.
For a more concrete example, I have a FruitFact class with members _uniqueID
, _type
, and _color
. equals()
and hashCode()
are overridden:
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof FruitFact))
return false;
FruitFact other = (FruitFact) obj;
if (this.getType() == null) {
if (other.getType() != null)
return false;
} else if (!this.getType().equals(other.getType()))
return false;
if (this.getColor() == null) {
if (other.getColor() != null)
return false;
} else if (!this.getColor().equals(other.getColor()))
return false;
return true;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.getType() == null) ? 0 : this.getType().hashCode());
result = prime * result
+ ((this.getColor() == null) ? 0 : this.getColor().hashCode());
return result;
}
I insert 2 logically unequal FruitFacts into WM:
FruitFact f1 = new FruitFact(1, "APPLE", "RED");
FruitFact f2 = new FruitFact(2, "APPLE", "GREEN");
wm.insert(f1);
wm.insert(f2);
My .drl file looks like this:
rule "Make non-red apples red"
salience 50
when
$f: FruitFact(color != "RED")
then
$f.setColor("RED");
update($f);
end
rule "retract"
salience 30
when
$f: FruitFact(id == 2)
then
retract($f);
end
rule "arraylist"
salience 10
when
$fList: ArrayList(size > 0) from collect(FruitFact())
then
System.out.println("$fList: " + $fList.toString());
ArrayList $fListCopy = new ArrayList($fList);
Iterator it = $fListCopy.iterator();
while (it.hasNext()) {
FruitFact $f = (FruitFact) it.next();
retract($f);
}
end
The above throws a FactException. The print statement in rule "arraylist"
shows that the only item in the ArrayList is the FruitFact that was already deleted (ie, the FruitFact with id=2):
$fList: [FruitFact {_id=2, _type=APPLE, _color=RED}]
The other unusual thing is that the other FruitFact (id=1) is not getting matched by the rule. If I replace rule "arraylist"
with a single pattern rule,
rule "single pattern - after retract"
salience 10
when
$f: FruitFact()
then
System.out.println("$f: " + $f);
retract($f);
end
The rule fires once to retract only FruitFact with id=1, and no exception is thrown, which is what I would expect.
Is there something wrong in my implementation of my FruitFact class or my rules? Why would this happen only for "from collect" rules and not single patterns? Why does it only manifest for Facts that become logically equivalent in WM?
Any insight would be appreciated. For reference, I am using Drools 5.5.0.Final.