Rule Attributes

Drools rules are data-driven. This means that the only way to activate a rule is by adding data to the engine that matches the conditions of that rule. However, there are multiple circumstances where we will want some rules with matching situations to be filtered out. One of this filtering mechanism is called rule attributes.

Rule attributes are extra features that we add to our rules to modify their behavior in a specific way. There are many rule attributes (of which, we’ll explain the most used ones and some of their possible combinations) and each one modifies the way we filter rules execution in a different way, as follows:

rule "simple attribute example"
enabled false
   when Customer()
   then System.out.println("we have a customer");
end

If the enabled attribute is set to false, the rule will be evaluated, however, it won’t be executed. It is perhaps the simplest example of a rule attribute shown here to see that the rule attributes are written before the when part of a rule. However, even if that’s their syntactic position, they come second to the conditions of the rule. This means that if the conditions of the rule don’t find a match for the working memory, attributes won’t even play a part in the execution. However, when a rule set of conditions matches a group of objects in the working memory, rule attributes will come as an extra component to decide whether this match should fire now, later, or not at all.

The following subsections will show some of the most commonly used rule attributes and how they influence the execution of our rules is shown as follows

  • Deciding the rule matches that will fire and the rule matches that won’t fire
  • Splitting our rules in groups, which might be valid or invalid in different situations
  • Controlling other special situations in which rules might or might not be valid

Example – controlling which rules will fire

When we define rules, the rule that matches the data with its conditions first will be the first in the list of rules to fire. This means that the order of the execution of rules is not deterministic. However, sometimes, we might need some rules to take precedence over the rest. For example, in our example about classifying items of rules, we saw in the previous chapter that we might have a specific subcategory in a special set of values between the mid range and we might want the cases where this rule finds a match to take precedence over the common mid-range classifying, as follows:

rule "Classify Item - Mid Range"
    when 
        $i: Item( cost > 200 && cost < 500, category == Category.NA )
    then
        $i.setCategory(Item.Category.MID_RANGE);
        update($i);
end
rule "Classify Item - Mid/High Range (special)"
   when
      $i: Item( cost > 300 && cost < 400,category == Category.NA )
   then
      $i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);
      update($i);
end

In this example, if we add an item with the cost being 350, the first rule might be evaluated before the second one. If we want the second rule to take precedence, we can set a higher priority to it using the salience rule attribute. The higher the salience, the higher the priority of the rule is, as shown in the following:

rule "Classify Item - Mid/High Range (special)"
salience 10
   ...
end

By default, all rules have an implicit salience attribute of 0 and you can assign positive or negative values to the salience attribute in order to execute them before or after the rest of the rules. Please take into account that rule attributes will only be evaluated after the rule conditions have matched with a group of data in the working memory, therefore, if the rule was not going to be triggered with the existing data, it won’t be triggered regardless of how high or low the salience value is set.

Rules have an implicit relative salience by default, that prioritizes the rules that appear earlier in the same DRL file. There’s no relative implicit salience between rules in different DRL files though.

There is a catch in the rule that we rewrote here, we stopped checking for the category attribute being set to NA. We did this on purpose in order to explain a common problem when getting started with Drools rules. As you can see, the rule consequence is updating the item and setting a category for it. Once update is called, the object will be re-evaluated for all the rules, including this one. This means that the item will be re-evaluated for this rule and if it still matches its condition (the cost being between 300 and 400), it will trigger the rule multiple times.

This sort of infinite loops can be managed in different ways. In the earlier versions, we checked whether, in the condition of the rule, the category was still NA. Once we modified the category, updating the object would trigger a re-evaluation of the rule, however, as it no longer has an NA category, it wouldn’t match the condition. This is the preferred way to do things when possible, however, if checking for a condition of this type becomes too complex, a rule attribute exists to let the engine know that a specific rule should not re-evaluate itself after it modifies the working memory. This attribute is the no-loop attribute, as shown in the following:

rule "Classify Item - Mid/High Range (special)"
no-loop
salience 10
   when
      ...
end

One other rule attribute that is very simple is the enabled rule attribute. It receives a boolean parameter to let the engine know whether the rule should be executed or not. If false, the rule is not evaluated, as follows:

rule "Classify Item - Mid/High Range (special)"
enabled true
    ...

This might seem like a weird rule attribute. Why would we want to use a rule attribute to disable a rule? You could just comment it out or delete the rule. In order to understand why it exists, we need to understand that rule attributes, even if they are written before the conditions of a rule, are always evaluated after the conditions of a rule. This means that they can use the data from the condition to decide the boolean value of the enabled attribute, integer value of the salience attribute, or any other attribute value that we might define in the future.

Given this information, we’re going to rewrite our rule with two different uses of variable values for rule attributes, we’re going to set the salience value based on the item’s cost from the condition and we’re going to set whether the rule is enabled or not based on a boolean method from a global variable, as follows:

global EShopConfigService configService;
...
rule "Classify Item - Mid/High Range (special)"
enabled(configService.isMidHighCategoryEnabled())
   ...
end

As you can see in the previous example, we’re defining the salience of the rule based on a variable numeric value (specifically, the cost of the item detected in the condition), and we’re setting the enabled attribute based on the return value of a boolean method in the global variable. As long as the condition is written in parentheses and Java code, the engine is capable of understanding them.

Rule dates management

There are times when we want our rules to be considered only in specific moments in time. Some rules, specially company policies, such as special discounts for retail, tax calculations, and holiday specials, make sense only on specific dates or days of the week. There are rule attributes with this very purpose and they allow you to control whether or not a rule is enabled at a specific point in time.

Two of these attributes, date-effective and date-expires, determine the start and end date for a specific rule to be enabled. If we assume the government establishes a specific tax to be added to every purchase from 2015 to 2020, this would be a good way to define this rule, as follows:

rule "Add special tax of 3%"
   date-effective "01-Jan-2015"
   date-expires "31-Dec-2020"
   ...
end

There is another type of common case, where we might need to switch the rule from enabled to disabled periodically or based on a specific date configuration. We use a special attribute called calendars to specify the days when a rule is enabled.

Also, we might need to retrigger a specific rule on a specific schedule as long as the condition of the rule continues to match the specific data. For this kind of situation, there is a timer rule attribute that allows you to set either cron or interval-based timers to your rules.

Here’s an example of these two types of attributes working together on some rules:

rule "Weekday notifications of pending orders"
calendars "weekdays"
timer (int:0 1h)
   when Order($id: orderId)
   then emailService.sendEmail("Pending order: "+$id);
end
rule "Weekend notifications of pending orders"
calendars "weekends"
timer (cron:0 0 0/8 * * ?)
   when Order($id: orderId)
   then emailService.sendEmail("Pending order: "+$id);
end

As you can see, these two rules do pretty much the same. If there is an order on the working memory, they send an e-mail through a helper class set as a global variable called emailService. The main difference between the two rules is provided by the rule attributes. The first rule will only be active on the days the weekdays calendar tells it to be, while the second rule will only be active on the weekends calendar. Also, each rule will fire at different rates as long as the condition is still fulfilled, the first rule will fire at one hour intervals (with zero time of delay for the first time) and the second rule will fire exactly at 00:00, 08:00, and 16:00 hours.

Controlling loops in rules

So far, we’ve seen some ways in which we can manage our rules to trigger new rule invocations. This will help us enormously in order to be able to split our rules into simple components that interact in the background through the data in the working memory. Powerful as it is, however, it can bring us a few extra complications along the line of the rules getting fired more times than we desire. Fortunately, Drools provides us with a set of elements to control rule execution from the very syntax where we define them.

The first and the most simple case where we can get into an infinite rule execution loop happens when a rule modifies the working memory in a way that it retriggers itself. Let’s see an example of this problem in the following:

rule "Apply 10% discount on notepads"
    when
        $i: Item(name == "notepad", $sp: salePrice)
    then
        modify($i) { setSalePrice($sp * 0.9); }
end

In this rule, our intention is to reduce the sale price of the notepads in our inventory. We just don’t change the value of our items, however, we also want to notify the engine that it changed. This is done using the modify keyword, as discussed earlier in this chapter, and we do it as we might have other rules that need to re-evaluate the item now that the price is different.

The problem is that if the modified object still matches the condition of this rule (and it does as its name is still notepad), it will also re-evaluate itself. This will lead to an infinite amount of rule executions for the same element.

The method to avoid this unwanted loop is a very simple attribute called, by no surprise, no-loop. The no-loop rule attribute prevents a rule from reactivating itself, irrespective of the changes the rule makes to the working memory. Its syntax is very simple, as the following example depicts:

rule "Apply 10% discount on notepads BUT ONLY ONCE"
    no-loop true
    ...
end

The true condition is optional, and writing no-loop is enough. The boolean parameter is there because, as we previously mentioned, it can be a variable from the context that determines whether or not this rule is to be set as no loop or not.

It’s worth mentioning that no-loop only prevents this rule from refiring for the same data if it was the last rule to fire. If another rule changes the working memory in a way that matches this rule again, the no-loop condition won’t prevent it from executing a second time. Sometimes, this is a desired behavior, however, if this isn’t the case, there are other types of loop-prevention strategies that we need to discuss.

Lock-On-Active

rule "Give extra 2% discount for orders larger than 15 items"
    no-loop true
    when $o: Order(totalItems > 15)
    then modify ($o) { increaseDiscount(0.02); }
end
rule "Give extra 2% discount for orders larger than $100"
    no-loop true
    when $o: Order(total > 100.00)
    then modify ($o) { increaseDiscount(0.02); }
end

These rules have the no-loop attribute so that even if they modify the order object, they won’t retrigger themselves. However, nothing is stopping them from activating each other. Therefore, the first rule will trigger the second one, which will trigger the first one again, and so on and so forth. This type of infinite loop requires something a bit stronger than the no-loop attribute.

One quick way of making sure that a rule doesn’t get retriggered for the same objects is to add an attribute called lock-on-active to the troublesome rules. Whenever a ruleflow-group becomes active, any rule within this group that has lock-on-active set to true will not be activated any more for the same objects. Irrespective of the origin of the update, the activation of a matching rule is discarded. The following is an example of one of the rules rewritten to use lock-on-active:

rule "Give extra 2% discount for orders larger than $100"
    lock-on-active true
    ...
end

In this second case, the rules will trigger only once for the same objects.

This is a stronger version of no-loop as the change can now be caused not only by the rule itself. It’s ideal for calculation rules, where you have a number of rules that modify a fact and you don’t want any rule re-matching and firing again. Only when the ruleflow-group is no longer active or the agenda group loses the focus; these rules, with lock-on-active set to true, become eligible again for matching on the same objects to be possible again.