/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.core.phreak;
import java.util.Comparator;
import org.drools.core.base.SalienceInteger;
import org.drools.core.common.AgendaItem;
import org.drools.core.common.DefaultAgenda;
import org.drools.core.common.EventFactHandle;
import org.drools.core.common.EventSupport;
import org.drools.core.common.InternalAgenda;
import org.drools.core.common.InternalFactHandle;
import org.drools.core.common.InternalWorkingMemory;
import org.drools.core.conflict.PhreakConflictResolver;
import org.drools.core.definitions.rule.impl.RuleImpl;
import org.drools.core.reteoo.PathMemory;
import org.drools.core.reteoo.RuleTerminalNode;
import org.drools.core.reteoo.RuleTerminalNodeLeftTuple;
import org.drools.core.spi.Activation;
import org.drools.core.spi.Consequence;
import org.drools.core.spi.ConsequenceException;
import org.drools.core.spi.InternalActivationGroup;
import org.drools.core.spi.KnowledgeHelper;
import org.drools.core.spi.Tuple;
import org.drools.core.util.BinaryHeapQueue;
import org.drools.core.util.index.TupleList;
import org.kie.api.event.rule.MatchCancelledCause;
import org.kie.api.runtime.rule.AgendaFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RuleExecutor {
protected static final transient Logger log = LoggerFactory.getLogger(RuleExecutor.class);
private static final RuleNetworkEvaluator NETWORK_EVALUATOR = new RuleNetworkEvaluator();
private final PathMemory pmem;
private final RuleAgendaItem ruleAgendaItem;
private final TupleList tupleList;
private BinaryHeapQueue queue;
private volatile boolean dirty;
private final boolean declarativeAgendaEnabled;
private boolean fireExitedEarly;
public RuleExecutor(final PathMemory pmem,
RuleAgendaItem ruleAgendaItem,
boolean declarativeAgendaEnabled) {
this.pmem = pmem;
this.ruleAgendaItem = ruleAgendaItem;
this.tupleList = new TupleList();
this.declarativeAgendaEnabled = declarativeAgendaEnabled;
if (ruleAgendaItem.getRule().getSalience().isDynamic()) {
queue = new BinaryHeapQueue(SalienceComparator.INSTANCE);
}
}
public void evaluateNetwork(InternalAgenda agenda) {
NETWORK_EVALUATOR.evaluateNetwork( pmem, this, agenda );
setDirty( false );
}
public int evaluateNetworkAndFire( InternalWorkingMemory wm,
AgendaFilter filter,
int fireCount,
int fireLimit ) {
reEvaluateNetwork( wm );
return fire(wm, wm.getAgenda(), filter, fireCount, fireLimit);
}
public int evaluateNetworkAndFire( InternalAgenda agenda,
AgendaFilter filter,
int fireCount,
int fireLimit ) {
reEvaluateNetwork( agenda );
return fire(agenda.getWorkingMemory(), agenda, filter, fireCount, fireLimit);
}
public void fire(InternalAgenda agenda) {
fire(agenda.getWorkingMemory(), agenda, null, 0, Integer.MAX_VALUE);
}
private int fire( InternalWorkingMemory wm,
InternalAgenda agenda,
AgendaFilter filter,
int fireCount,
int fireLimit) {
int localFireCount = 0;
if (!tupleList.isEmpty()) {
if (!fireExitedEarly && isDeclarativeAgendaEnabled()) {
// Network Evaluation can notify meta rules, which should be given a chance to fire first
RuleAgendaItem nextRule = agenda.peekNextRule();
if (!isHigherSalience( nextRule )) {
fireExitedEarly = true;
return localFireCount;
}
}
RuleTerminalNode rtn = (RuleTerminalNode) pmem.getPathEndNode();
RuleImpl rule = rtn.getRule();
Tuple tuple = getNextTuple();
if (rule.isAllMatches()) {
fireConsequenceEvent(wm, agenda, (AgendaItem) tuple, DefaultAgenda.ON_BEFORE_ALL_FIRES_CONSEQUENCE_NAME);
}
Tuple lastTuple = null;
for (; tuple != null; lastTuple = tuple, tuple = getNextTuple()) {
//check if the rule is not effective or
// if the current Rule is no-loop and the origin rule is the same then return
if (cancelAndContinue(wm, rtn, rule, tuple, filter)) {
continue;
}
AgendaItem item = (AgendaItem) tuple;
if (agenda.getActivationsFilter() != null && !agenda.getActivationsFilter().accept(item, wm, rtn)) {
// only relevant for seralization, to not refire Matches already fired
continue;
}
fireActivation( wm, agenda, item );
localFireCount++;
if (rtn.getLeftTupleSource() == null) {
break; // The activation firing removed this rule from the rule base
}
agenda.flushPropagations();
int salience = ruleAgendaItem.getSalience(); // dyanmic salience may have updated it, so get again.
if (queue != null && !queue.isEmpty() && salience != queue.peek().getSalience()) {
ruleAgendaItem.dequeue();
ruleAgendaItem.setSalience(queue.peek().getSalience());
ruleAgendaItem.getAgendaGroup().add( ruleAgendaItem );
}
if (!rule.isAllMatches()) { // if firing rule is @All don't give way to other rules
if ( haltRuleFiring( fireCount, fireLimit, localFireCount, agenda ) ) {
break; // another rule has high priority and is on the agenda, so evaluate it first
}
if (!wm.isSequential()) {
reEvaluateNetwork( agenda );
}
}
}
if (rule.isAllMatches()) {
fireConsequenceEvent(wm, agenda, (AgendaItem) lastTuple, DefaultAgenda.ON_AFTER_ALL_FIRES_CONSEQUENCE_NAME);
}
}
removeRuleAgendaItemWhenEmpty(wm);
fireExitedEarly = false;
return localFireCount;
}
private Tuple getNextTuple() {
if (tupleList.isEmpty()) {
return null;
}
Tuple leftTuple;
if (queue != null) {
leftTuple = (Tuple) queue.dequeue();
tupleList.remove(leftTuple);
} else {
leftTuple = tupleList.removeFirst();
((Activation) leftTuple).setQueued(false);
}
return leftTuple;
}
public PathMemory getPathMemory() {
return pmem;
}
public void removeRuleAgendaItemWhenEmpty(InternalWorkingMemory wm) {
if (!dirty && tupleList.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("Removing RuleAgendaItem " + ruleAgendaItem);
}
ruleAgendaItem.remove();
if ( ruleAgendaItem.getRule().isQuery() ) {
wm.getAgenda().removeQueryAgendaItem( ruleAgendaItem );
} else if ( ruleAgendaItem.getRule().isEager() ) {
wm.getAgenda().removeEagerRuleAgendaItem(ruleAgendaItem);
}
}
}
public void reEvaluateNetwork(InternalWorkingMemory wm) {
reEvaluateNetwork(wm.getAgenda());
}
public void reEvaluateNetwork(InternalAgenda agenda) {
if ( isDirty() ) {
setDirty(false);
NETWORK_EVALUATOR.evaluateNetwork(pmem, this, agenda);
}
}
public RuleAgendaItem getRuleAgendaItem() {
return ruleAgendaItem;
}
private boolean cancelAndContinue(InternalWorkingMemory wm,
RuleTerminalNode rtn,
RuleImpl rule,
Tuple leftTuple,
AgendaFilter filter) {
// NB. stopped setting the LT.object to Boolean.TRUE, that Reteoo did.
if ( !rule.isEffective(leftTuple, rtn, wm) ) {
return true;
}
if (rule.getCalendars() != null) {
long timestamp = wm.getSessionClock().getCurrentTime();
for (String cal : rule.getCalendars()) {
if (!wm.getCalendars().get(cal).isTimeIncluded(timestamp)) {
return true;
}
}
}
return filter != null && !filter.accept((Activation) leftTuple);
}
private boolean haltRuleFiring(int fireCount,
int fireLimit,
int localFireCount,
InternalAgenda agenda) {
if (!agenda.isFiring() || (fireLimit >= 0 && (localFireCount + fireCount >= fireLimit))) {
return true;
}
// The eager list must be evaluated first, as dynamic salience rules will impact the results of peekNextRule
agenda.evaluateEagerList();
RuleAgendaItem nextRule = agenda.peekNextRule();
return nextRule != null && (!ruleAgendaItem.getAgendaGroup().equals( nextRule.getAgendaGroup() ) || !isHigherSalience(nextRule));
}
private boolean isHigherSalience(RuleAgendaItem nextRule) {
return PhreakConflictResolver.doCompare(ruleAgendaItem,nextRule) >= 0;
}
public TupleList getLeftTupleList() {
return tupleList;
}
public void addLeftTuple(Tuple tuple) {
((AgendaItem) tuple).setQueued(true);
this.tupleList.add(tuple);
if (queue != null) {
addQueuedLeftTuple(tuple);
}
}
public void addQueuedLeftTuple(Tuple tuple) {
int currentSalience = queue.isEmpty() ? 0 : queue.peek().getSalience();
queue.enqueue((Activation) tuple);
updateSalience(currentSalience);
}
public void removeLeftTuple(Tuple tuple) {
((AgendaItem) tuple).setQueued(false);
this.tupleList.remove(tuple);
if (queue != null) {
removeQueuedLeftTuple(tuple);
}
}
private void removeQueuedLeftTuple(Tuple tuple) {
int currentSalience = queue.isEmpty() ? 0 : queue.peek().getSalience();
queue.dequeue(((Activation) tuple));
updateSalience(currentSalience);
}
private void updateSalience(int currentSalience) {
// the queue may be emtpy if no more matches are left, so reset it to default salience 0
int newSalience = queue.isEmpty() ? SalienceInteger.DEFAULT_SALIENCE.getValue() : queue.peek().getSalience();
if (currentSalience != newSalience) {
// salience changed, so the RuleAgendaItem needs to be removed and re-added, for sorting
ruleAgendaItem.remove();
}
if (!ruleAgendaItem.isQueued()) {
ruleAgendaItem.setSalience(newSalience);
ruleAgendaItem.getAgendaGroup().add(ruleAgendaItem);
}
}
public void cancel(InternalWorkingMemory wm, EventSupport es) {
while (!tupleList.isEmpty()) {
RuleTerminalNodeLeftTuple rtnLt = (RuleTerminalNodeLeftTuple) tupleList.removeFirst();
if (queue != null) {
queue.dequeue(rtnLt);
}
es.getAgendaEventSupport().fireActivationCancelled(rtnLt, wm, MatchCancelledCause.CLEAR);
}
}
public boolean isDirty() {
return dirty;
}
public void setDirty(final boolean dirty) {
this.dirty = dirty;
}
public boolean isDeclarativeAgendaEnabled() {
return this.declarativeAgendaEnabled;
}
public static class SalienceComparator implements Comparator {
public static final SalienceComparator INSTANCE = new SalienceComparator();
public int compare(Object existing, Object adding) {
RuleTerminalNodeLeftTuple rtnLt1 = (RuleTerminalNodeLeftTuple) existing;
RuleTerminalNodeLeftTuple rtnLt2 = (RuleTerminalNodeLeftTuple) adding;
final int s1 = rtnLt1.getSalience();
final int s2 = rtnLt2.getSalience();
// highest goes first
if (s1 > s2) {
return 1;
} else if (s1 < s2) {
return -1;
}
final int l1 = rtnLt1.getRule().getLoadOrder();
final int l2 = rtnLt2.getRule().getLoadOrder();
// lowest goes first
if (l1 < l2) {
return 1;
} else if (l1 > l2) {
return -1;
} else {
return 0;
}
}
}
public void fireActivation(InternalWorkingMemory wm, InternalAgenda agenda, Activation activation) throws ConsequenceException {
// We do this first as if a node modifies a fact that causes a recursion
// on an empty pattern
// we need to make sure it re-activates
wm.startOperation();
try {
wm.getAgendaEventSupport().fireBeforeActivationFired( activation, wm );
if ( activation.getActivationGroupNode() != null ) {
// We know that this rule will cancel all other activations in the group
// so lets remove the information now, before the consequence fires
final InternalActivationGroup activationGroup = activation.getActivationGroupNode().getActivationGroup();
activationGroup.removeActivation( activation );
agenda.clearAndCancelActivationGroup( activationGroup);
}
activation.setQueued(false);
try {
innerFireActivation( wm, agenda, activation, activation.getConsequence() );
} finally {
// if the tuple contains expired events
for ( Tuple tuple = activation.getTuple(); tuple != null; tuple = tuple.getParent() ) {
if ( tuple.getFactHandle() != null && tuple.getFactHandle().isEvent() ) {
// can be null for eval, not and exists that have no right input
EventFactHandle handle = (EventFactHandle) tuple.getFactHandle();
// decrease the activation count for the event
handle.decreaseActivationsCount();
// handles "expire" only in stream mode.
if ( handle.expirePartition() && handle.isExpired() ) {
if ( handle.getActivationsCount() <= 0 ) {
// and if no more activations, retract the handle
handle.getEntryPoint().delete( handle );
}
}
}
}
}
wm.getAgendaEventSupport().fireAfterActivationFired( activation, wm );
} finally {
wm.endOperation();
}
}
public void fireConsequenceEvent(InternalWorkingMemory wm, InternalAgenda agenda, Activation activation, String consequenceName) {
Consequence consequence = activation.getRule().getNamedConsequence( consequenceName );
if (consequence != null) {
fireActivationEvent(wm, agenda, activation, consequence);
}
}
private void fireActivationEvent(InternalWorkingMemory wm, InternalAgenda agenda, Activation activation, Consequence consequence) throws ConsequenceException {
wm.startOperation();
try {
innerFireActivation( wm, agenda, activation, consequence );
} finally {
wm.endOperation();
}
}
private void innerFireActivation( InternalWorkingMemory wm, InternalAgenda agenda, Activation activation, Consequence consequence ) {
try {
KnowledgeHelper knowledgeHelper = agenda.getKnowledgeHelper();
knowledgeHelper.setActivation( activation );
if ( log.isTraceEnabled() ) {
log.trace( "Fire event {} for rule \"{}\" \n{}", consequence.getName(), activation.getRule().getName(), activation.getTuple() );
}
wm.getRuleEventSupport().onBeforeMatchFire( activation );
consequence.evaluate(knowledgeHelper, wm);
wm.getRuleEventSupport().onAfterMatchFire( activation );
activation.setActive(false);
knowledgeHelper.cancelRemainingPreviousLogicalDependencies();
knowledgeHelper.reset();
} catch ( final Exception e ) {
agenda.handleException( wm, activation, e );
} finally {
if ( activation.getActivationFactHandle() != null ) {
// update the Activation in the WM
InternalFactHandle factHandle = activation.getActivationFactHandle();
wm.getEntryPointNode().modifyActivation( factHandle, activation.getPropagationContext(), wm );
activation.getPropagationContext().evaluateActionQueue( wm );
}
}
}
}