TODO Fix all mispellings of temperature (ie, tempurature) both in this text and the classes.
In this tutorial I will the build HVAC example from Jess In Action (JIA) using drools-spring. The HVAC application will consist of a main class that creates a spring ApplicationContext and asserts the initial facts, a set of domain abstractions (eg, Vent, HeatPump, Floor) that serve as facts, a simulator that models heat transfer between the floors of a building, and a set of rules that react to changes in temperature by adjusting the state of Vents and HeatPumps.
All the rules are implemented as simple POJOs with annotations and declared in the spring config file. Drools .drl files are not used by the spring integration. The rules and system components are created and initialized by spring. All dependencies between components are wired using spring's dependency injection mechanisms.
Because everything in drools-spring is a POJO, you get to use the full power your IDE as well test-first using junit. Most of the HVAC application was developed test-first with exception of the simulator and the GUI which I mainly copied directly from the JIA example. Otherwise test coverage is fairly complete.
While this tutorial uses and requires Java SE 5+, drools-spring does not depend on Java 5 at all. Drools-spring uses pluggable strategies for obtaining rule-metadata and provides a growing set of Java 1.4 compatible strategies. The drools-spring-jdk5 module extends drools-spring with Tiger assertions for declaring rules, conditions, and consequences, along with the related strategies. More on this later.
The tutorial is organized in two parts. In the Part 1, The HVAC Application, I describe and build the HVAC application using only those drools-spring features required for the HVAC application. Specifically, all rule metadata will be specified using Java 5 annotations, even though drools-spring has equivalent support for Java 1.4. In Part 2, Drools Spring Strategies, I will outline additional drools-spring implementation strategies. Part 2 will also provide links to other documentation and javadocs.
Part 1, The HVAC Application
Since I won't cover every detail of the application in this tutorial, if you want to run the application and browse the code, you will need to download the drools-spring-examples project from the drools cvs server. Drools projects are built with maven. If you plan on building and running the HVAC appliation, you will need to have maven installed.
... TODO describe how to download and build ...
... TODO describe the required dependencies ...
The Domain Model
The domain for the HVAC application consists of
TempuratureControl. We don't model the building that contains the floors directly since the rules do not depend on that abstraction. All of these abstractions are declared as interfaces, with implemenations provided by the
... TODO include a diagram like JIA figure 14.1 ...
Floor represents a single floor in the building. The
Floor keeps track of its floor number, its
Thermometer is sensor that detects the tempurature of its floor. Its
getReading method provides the current tempurature.
Thermometer implements the JavaBeans property-change protocol methods which allows drools to detect changes to the tempurature and automatically update
WorkingMemory with the new value. The simulator implements this interface and updates the
Thermometer based on its heat transfer model.
Vent controls the flow of heated or cooled onto the floor. A
Vent can either be
CLOSED. Rules open and close the
Vent according to the need to adjust the tempurature on a floor.
HeatPump exchanges heat between floors and the outside of the building. A
HeatPump can be either
OFF. A single
HeatPump can serve multiple floors. Rules change the
HeatPump state according to the need to adjust the tempurature on a floor.
TempuratureControl sets the desired tempurature for the entire building, represented by the property
setPoint. Rules use
TempuratureControl to determine whether a floor is too hot or too cold. To keep the system from thrashing between cooling and heating,
TempuratureControl uses the property
guardAmount to define "guard lines" around the desired tempurature. The current tempurature must be above or below the
guardAmount before it will be considered too hold or too cold.
TempuratureControl exports two concepts: Whether a floor is cool or warm enough, and whether a floor is too cool or too hot. Cool enough or warm enough is when the tempurature has reached the
setPoint but has not gone beyond the
guardPoint. Too cool or tool hot is when the tempurature has gone beyond the
setPoint by the
guardAmount. As we will see in the next section, the rules use these two concepts decide how aggressive to be in attempting to adjust the tempurature on a floor.
... TODO should this next para be here or later when we describe the rules ...
In the current implementation, the values for
guardAmount are specified in
hvac.properties and cannot be changed at runtime.
TempuratureControl is not fact, but rather injected as a dependency into the rules. If we wanted to change
guardAmount at runtime, we would need to make
TempuratureControl a fact so that rules fired upon the property change. But I have left it this way so I could demonstrate dependency injection into rules.
The HVAC Rules
(Note: The rule design was lifted directly out of JIA, so I won't make any attempt to explain the whys or alternatives. I recommend that you read JIA, its an excellent book.)
The rules are seperated into two groups, or rule-sets. One set for controlling the heat pumps, and another for controlling the vents.Controlling the Heat Pumps
There are four rules that control the heat pumps.
PumpOffWhenFloorsWarnEnough ensure that the heat pump is turned off when a floor's tempurature reaches the desired level (ie, the
setPoint defined by
PumpCoolingWhenFloorTooHot, sets the pump state to
COOLING when the floor becomes too hot. Remember that "too hot" means that the tempurature has passed the
guardAmount. In other words, we don't want to turn the pump on immediatly after desired tempurature has been reached to avoid hystersis and thus damaging the pump. Similary,
PumpHeatingWhenFloorTooCold sets the pump state to
HEATING when the floor becomes too cold.
PumpCoolingWhenFloorTooHot first. The first step is to create a POJO class annotated with @Rule.
@Rule annotation declares the POJO to be a drools rule.
@Rule allows for rule metadata to be declared in the annotation. For example:
As we will soon see, the conditions in this rule will need access to the buildings
TemperatureControl. Recall from the domain model that
TemperatureControl maintains the buildings desired tempurature, as well as exporting methods that determine if a given temperature is too cold, too hot, etc. We declare
TemperatureControl as a java beans property on the rule. We'll see how this property get injected into the rule when we cover the spring configuration.
Next comes the condition methods. We only want this rule to fire (ie, turn the pump to
COOLING) if the pump is currently in the
@Condition annotation declares a method to be a rule condition. The method must have the signature "
Condition methods are invoked by the drools rete engine, therefore the arguments passed to them must be known to the engine. Three types of condition parameters are supported: facts, application-data, and the special class KnowledgeHelper. When using annotations, facts are specified using the
@Fact parameter annotation. Drools-spring can also infer condition argument types using various pluggable strageties. Throughout the HVAC application, all condition parameters will be facts, so we will omit
@Fact and specify an appropriate strategy in the spring configuration (covered later on).
The next condition for this rule is that the
Thermometer tempurature reading "is too hot". We check this by invoking the method
Recall from the domain model that a
HeatPump serves three adjacent
Floors. So we need to ensure that the
HeatPump we determined to be
OFF is serving the the
Floor of the
Thermometer instance checked by the
isTooHot condition method.
You might be tempted to think that we are doing to much work by checking too many permutations. But because of the way Rete networks are built, a condition is tested for a given set of facts only once, and the calculated result stored until a fact is actually modified.
Finally, we need to specify the rule consequence. When our conditions have determined that the floor's temperature is too hot, and the floor's heat pump is off, then we set the heat pump cooling.
@Consequence annotations marks the method as a rule consequence. And here again we see
HeatPump specified as a parameter. Similar to conditions, the spring configuration will be setup such that the consequence parameters without annotations will be considered facts. Since
HeatPump is a fact asserted into working-memory, the change in its state will propagate through the Rete network, causing conditions to fire, and possibly activating other rules.
Here is complete code for
The spring application context config file must define the drools beans and HVAC application beans. The main class will create a spring application using this config file an start the HVAC simulation running.
The first step is to define the rule-sets for the heat-pump rules and vent rules. A rule-set bean is created with
RuleSetFactoryBean. The rules are specified with the
This will result in the creation a
RuleSet composed of the four heat-pump rules. We'll cover the details of rule bean shortly. For each pojo rule,
RuleSetFactoryBean will build a corresponding internal drools
Rule. In order to do this, we need to tell
RuleSetFactoryBean how to obtain the rule metadata, of which there are three kinds. 1) Rule metadata, such as the rule name and saliance; 2) Method metadata, which indicates which methods in the rule pojo are conditions and which are consequences; 3) Argument metatada, which indicates for each condition/consequence parameter whether its a fact or application-data.