Skip to main content
Jump to: navigation, search

VIATRA/CEP/Examples/SimpleEventProcessing

< VIATRA‎ | CEP‎ | Examples

Example #1: Simple event processing

The sources are available from the VIATRA Examples repository.

We are about to design a security system which controls secret compartments in a real estate. The system is rather old-school. For example, one would open a drawer first then turn on the light and knock on the wall twice to open a compartment. For that purpose, sensors are installed on the significant points of the real estate which emit events if something interesting happens; and a central controller deals with the events. The essentials of complex event modeling and processing are obvious even in this plain simple example:

  • we would like to depict the interesting happenings as atomic events (e.g. a knocking on the wall) and
  • complex events (e.g. a series of events which would open a secret compartment), then
  • define actions to be executed based on identified complex event patterns (e.g. to open a secret compartment), and finally,
  • employ some sort of a central reasoning system to process atomic events and identify complex event patterns.

Modeling atomic events

We follow the conventions of the original example, i.e. identify atomic events using four-character IDs. We have different sensors on stock: a door sensor (DO), a drawer sensor (DW), a light swich sensor (LI) and a wall sensor (WL). The door is either in a closed (DOCL) or open (DOOP) state. On every state change, an event is triggered with the given four-character ID. The same applies for the drawer (DWOP, DWCL). Similarly, the light can be turned on (LION) or off (LIOF). Finally, every knock on the wall triggers a separate event (WLKC). The outlined events will serve as the atomic events in our example.

Let's define the above atomic events using VEPL, i.e. the VIATRA-CEP Event Processing Language.

atomicEvent DOCL
atomicEvent DOOP
atomicEvent DWCL
atomicEvent DWOP
atomicEvent LIOP
atomicEvent LIOF
atomicEvent WLKC

The complete source is available here. (For the sake of simplicity, the events do not feature parameters, nor any special features, hence the empty parentheses and braces. For details see the language overview.)

Modeling complex event patterns

Setting up the security system is a twofold task: (i) we have to deploy the mentioned sensors, and (ii) we have to configure them. The latter on means that we have to define the complex event patterns to depict the "security codes" of the customer; and we also have to define the actions triggered when these complex event patterns are identified.

Let's say, the first secret compartment will open (SC1O) when the following sequence of events is observed: the door opens, the lights go on and the drawer is opened. Using VEPL, this complex event looks like this:

complexEvent SC1O(){
     as DOOP -> LION -> DWOP 
}

The pattern begins with the keyword ComplexEvent, then a unique name follows. Again, the parentheses are empty because we do not use parameters in our patterns. To see a parameterized example, jump here. The definition features a very straightforward operator, the followed by operator, denoted by an arrow (->). The complex event will be observed if all the required atomic events occur in this order.

We want to allow the second compartment to be opened by two knocks on the wall:

complexEvent SC2O(){
     as WLKC{2}
}

Here, the {2} after the atomic event denotes its required multiplicity. It would be equivalent to write it in this form:

complexEvent SC2O(){
     as WLKC -> WLKC
}

Finally, let's say the third secret compartment can be opened in two ways. One either has to turn the light on and subsequently off within a second, or has to open and then subsequently close the drawer twice within two seconds.

complexEvent SC3O(){
     as ((LION -> LIOF)[1000] OR (DWOP -> DWCL){2}[2000])
}

The definition is actually a combination of two complex event patterns, connected with an or-clause (OR). The first part (on the left side of the OR operator) defines the case when the light is turned on and off again within a second. Timing criteria can be defined between brackets: the number will define an upper limit for the pattern it follows to appear within. Timing and multiplicity criteria can be combined, as the second part of the definition shows it.

Actions

To actually open the secret compartments when the appropriate complex events are observed, we define actions in which we activate an appropriate actuator. The logic of the actuator is pretty simple, we only have to invoke its openCompartment method with the appropriate compartment number, then we will see a message about the appropriate compartment being opened:

public class CompartmentActuator {
     public static void openCompartment(int compartmentNumber) {
          System.out.println(String.format("Opening compartment #%d.", compartmentNumber));
     }
}

The actual method invocation is placed into an block called rule:

rule openSC1 on SC1O{
     CompartmentActuator::openCompartment(1)
}

In the above rule, we invoke the actuator with the compartment number "1", since the action will be triggered by the complex event SC1O, which describes the required pattern to open compartment #1. The action block is defined using Xbase syntax. [ref?] The other two rules look pretty similar, only the required event and the parameter of the actuator is changed.

Putting things into motion

Finally, we have to set up the system by reusing the modeled events, event patterns and actions. We will demonstrate this with a set of simple JUnit tests. The full source can be found here.

First, we have to obtain an instantiate of the CEP engine from the framework. This can be done using the engine's builder API:

CEPEngine.newEngine().eventContext(eventContext).rules(rules).prepare(); (Source)

The above line will (i) obtain a new, uninitialized engine instance; (ii) set the event context for the processing; and finally, (iii) initialize the engine with the CEP rules.

Rule instances can be obtained from the CepFactory utility class:

CepFactory.getInstance().createOpenSC1()

Or to use every rule:

 CepFactory.getInstance().allRules()

Now we can forward events to the engine. To do so, we need an event stream, which can be acquired from the instantiated engine.

EventStream eventStream = engine.getStreamManager().newEventStream();

The above steps can be reduced by defining our application as an extension of the DefaultApplication helper class:

public class Basics extends DefaultApplication {
     ...
}

Finally, we will forward the three atomic events required for the SC1O complex event pattern, in the right order, using the previously seen factory class.

eventStream.push(CepFactory.getInstance().createDOOP_Event());
eventStream.push(CepFactory.getInstance().createLION_Event());
eventStream.push(CepFactory.getInstance().createDWOP_Event());

Running the test will now give the expected result, that is, the complex event pattern will be recognized.

Opening compartment #1.

Parameterized event patterns

Atomic event patterns can be augmented with parameters evaluated at run time. This enables defining even more complex event patterns. Parameters are typed and to this end, the whole JVM type hierarchy is available.

Let's modify the example and say that there are multiple drawers in the room and in order to open the third compartment the same drawer should be opened and closed twice. Complex event pattern SC3O tries do depict this case, but it will fall short as it does not specify that the same drawer should be manipulated. To overcome this, let's introduce an ID parameter in the atomic event pattern:

atomicEvent DWCL_2(id:String){}
atomicEvent DWOP_2(id:String){}

The two patterns are the counterparts of the DWCL and DWOP patterns, only with parameters. Now the SC3O pattern can be extended as well with a parameter and this parameter can be used to specify that we want to manipulate the same drawer:

ComplexEvent SC3O_2(drawerId:String){
     definition: ((LION -> LIOF)[1000] OR (DWOP_2(drawerId)->DWCL_2(drawerId)){2}[2000])
}

Let's rewrite the example application. After instantiating an Event class, it's ID should be also defined:

DWOP_2_Event dwop_Event_1 = CepFactory.getInstance().createDWOP_2_Event();
dwop_Event_1.setId("id_drawer#1");

DWCL_2_Event dwcl_Event_1 = CepFactory.getInstance().createDWCL_2_Event();
dwcl_Event_1.setId("id_drawer#1");

DWOP_2_Event dwop_Event_2 = CepFactory.getInstance().createDWOP_2_Event();
dwop_Event_2.setId("id_drawer#1");

DWCL_2_Event dwcl_Event_2 = CepFactory.getInstance().createDWCL_2_Event();
dwcl_Event_2.setId("id_drawer#1");

Now pushing these events to the event stream in the desired sequence, we'll get the expected result.

getEventStream().push(dwop_Event_1);
getEventStream().push(dwcl_Event_1);
getEventStream().push(dwop_Event_2);
getEventStream().push(dwcl_Event_2);
Opening compartment #3.

Omitting a parameter in an atomic pattern of the definition will bypass the dynamic parameter checking. Let's specify different IDs to the drawer events:

DWOP_2_Event dwop_Event = CepFactory.getInstance().createDWOP_2_Event();
dwop_Event.setId("drawer#1");
DWCL_2_Event dwcl_Event = CepFactory.getInstance().createDWCL_2_Event();
dwcl_Event.setId("drawer#2");

Despite pushing the events to the event stream in the appropriate sequence, the complex event pattern won't be recognized. If the parameter of the first or the second atomic pattern is omitted, however, that atomic pattern won't be checked for the parameter and therefore, it will not matter what the ID of the other pattern was.

ComplexEvent SC3O_2(drawerId:String){
     definition: ((LION -> LIOF)[1000] OR (DWOP_2(_)->DWCL_2(drawerId)){2}[2000])
}

Omitting a parameter is achieved by using a name starting with an underscore ("_") character. To omit every parameter, i.e. the whole parameter list, the block between parentheses can be omitted:

ComplexEvent SC3O_2(drawerId:String){
     definition: ((LION -> LIOF)[1000] OR (DWOP_2->DWCL_2(drawerId)){2}[2000])
}

Now the complex event pattern will be recognized again. The full source can be found here.

Traits

To abstract common traits of atomic event patterns, traits can be used. (Only attributes are supported currently.) The above example of parameterized events can be rewritten into:

trait identifiable{
     id : String
}
atomicEvent DWCL_3 with identifiable{
     check{
           id.startsWith("dw")
     }
}
atomicEvent DWOP_3 with identifiable{
     check{
           id.startsWith("dw")
     }
}

Back to the top