1. Overview

1.1. Sponge

Sponge is an open-source, Java-based Action and Event Processing System.

This documentation applies to version 1.6.0 (release date: 2019-01-11).

Sponge is a software for implementing synchronous actions and asynchronous event processors. Event processors listen to certain events and perform specified operations when some conditions are met. Events are usually sent by external systems. Sponge allows to introduce temporal and logical conditions to processed events. Event processing may end up with sending of another, high-level event thus creating a hierarchical model of events. The supported event processors include filters, triggers, rules and correlators.

There are several ways to use Sponge:

  • Embed Sponge as a software library in your Java application using Maven dependency.

  • Download the Sponge standalone command-line program and use it in your environment.

  • Run the Sponge standalone command-line program in a Docker container.

  • Connect to an existing Sponge instance via the REST API or the Sponge mobile client application (currently under development).

Actions and event processors are grouped in knowledge bases. A knowledge base is defined in files which may be written in one of several supported scripting languages, i.e. Python, Ruby, Groovy, JavaScript as well as in Java or Kotlin.

In some aspects Sponge may be classified as a Complex Event Processing.

Sponge requires Java 1.8 or above.

The project pages are hosted at https://sponge.openksavi.org. The source code is hosted at Github.

Sponge has been created by Softelnet.

Sponges draw in a current of water to extract nutrients and oxygen.

— Oxford Dictionaries
Sponge

1.2. Features

The main features of Sponge:

  • Provides a concurrent processing of actions and incoming events.

  • Allows generating and scheduling of events (e.g. supports cron notation).

  • Events may represent such diverse entities as, for example, SNMP traps, RSS feeds or MIDI messages.

  • Allows embedding in a custom Java application (using Maven) or could be used as a standalone command-line application.

  • Supports connecting to many types of external systems by using Apache Camel integration.

  • Provides own Apache Camel component for including Sponge engine in a Camel route.

  • Provides plugins for integration with Spring, Camel, CPython, MIDI, MPD and Raspberry Pi.

  • Supports creation of custom plugins.

  • Supports deployment in enterprise environments by integration with Spring framework.

  • The standalone command-line application allows, for example, management of Camel routes without the need of writing Java code (you may use any of the supported scripting languages or XML).

  • Supports reloading of knowledge bases on the fly (i.e. while the system is running).

  • Supports processing of streams of Sponge events by using Reactive Extensions (ReactiveX).

  • Allows publishing a custom REST API of your application as a set of actions (e.g. written in a scripting language) that may be changed and reloaded on the fly.

  • Provides REST API client libraries for Java and Dart to simplify writing a client code.

  • Provides integration with TensorFlow, an open source machine learning framework. You may use the TensorFlow or Keras Python API indirectly from your Java application.

  • Allows fast creating generic, opinionated mobile applications backed by a server-side business logic. The business logic has to be defined in Sponge actions and the end user only runs the Sponge mobile client application (currently under development) that provides a generic GUI to call actions. It could be useful in the cases when the mobile GUI doesn’t have to be customized.

  • Provides a comprehensive documentation and many examples.

1.3. License

The Sponge standard libraries are released under the Apache 2.0 license. However some optional Sponge libraries and the Sponge standalone command-line application are licensed under the GNU General Public License, Version 3 because they use optional, third-party libraries that require it.

1.4. Applications

Sponge could be used as a component in various types of applications. Here are some examples.

Task automation

Tasks could be programmed in any of the supported scripting languages as Sponge actions, published via the REST API and call remotely in the Sponge mobile client application.

Internet of Things (IoT)

Sponge as a part of an IoT gateway can locally process data provided by the devices in the field, thus only important data is sent to the central node where data is collected, stored and manipulated by enterprise applications.

Edge computing

Sponge may be used on edge devices (also known as smart devices), providing a computation platform that would be largely or completely performed on distributed device nodes. The requirement is that such device must have Java (JRE) installed. The example is Raspberry Pi with connected sensors.

Business Activity Monitoring (BAM)

Sponge embedded in a Spring based application may have an access to a service layer via Spring beans. Sponge could periodically run monitoring tasks and generate notification events if necessary.

Fault management

Sponge may be used to process faults or events sent by the network using a protocol such as SNMP. The processing may include creating issues into the issue tracking system (Trouble Ticket system). Sponge may use all protocols supported by Apache Camel or provided by custom plugins.

Network monitoring

Sponge may be used to monitor a web server by periodically sending an HTTP request to fetch a page. It may also provide more advanced checks by running for example Selenium scripts to verify a web application. When a problem is detected Sponge could send an email to the administrator.

Supervisory control and data acquisition (SCADA)

Sponge may be used as a part of Alarm handling as well as a part of a module that connects to the remote terminal units (RTUs) or the field sensors and actuators.

Distributed control system (DCS)

Sponge may be used as a part of the production control level to provide an event-driven monitoring.

System integration

Sponge may be used to provide a script-based integration using Apache Camel routes. Sponge could be connected to the Enterprise Service Bus (ESB) as well.

1.5. Users

The potential users of an embedded Sponge are Java developers.

The potential users of a standalone command-line Sponge application are:

  • Python, Ruby, Groovy or JavaScript developers,

  • Java developers (for example for prototyping),

  • system administrators with programming skills.

Because of Sponge is a Java-based solution, at least basic knowledge of Java is suggested. It becomes important when browsing Javadoc API, using Java libraries, analyzing exception stack traces, etc. Moreover, to effectively use Sponge for problems that require integration, a knowledge of Apache Camel becomes important.

1.6. Considerations

  • Because of Sponge doesn’t introduce its own notation for knowledge bases, it provides a shorter learning curve for users that know one of the supported scripting languages. However it could lack more sophisticated features and could be less optimized for certain uses than other solutions. For example you specify event conditions by writing any code in a scripting language that is to be executed when a matching event happens. On one hand it gives you a flexibility and on the other hand it doesn’t provide optimizations that could be possible if, for example, a strict temporal reasoning notation is used.

  • Sponge doesn’t provide persistence of events out of the box.

1.7. Author’s note

Softelnet has used Open Source Software in its commercial products for many years. Therefore we appreciate the impact of Open Source on the IT industry and believe that its constant development is important. In order to contribute to the Open Source community we share and maintain projects such as Sponge.

— Marcin Paś
Co-Founder & CTO at Softelnet

2. Quickstart

2.1. Concepts

This chapter describes basic concepts behind Sponge. For more information see User Guide.

2.1.1. Processors, actions and event processors

Processors are the basic objects that you define in Sponge to implement your knowledge base behavior. Actions provide a synchronous behavior. Event processors listen to certain events and perform specified operations when some conditions are met. Sponge allows to introduce temporal and logical conditions to processed events.

Event processors
Filter

Filters allow only certain events to be processed by other event processors (triggers, rules and correlators).

Trigger

Triggers execute specified operations when an appropriate event happens.

Rule

Rules detect situations when a sequence of events happens.

Correlator

Correlators detect correlations between events and could be used for implementing any complex event processing that isn’t provided by filters, triggers or rules.

2.1.2. Knowledge base

A knowledge base is a registry where processors are defined.

There are two types of knowledge bases:

  • scripting knowledge bases (i.e. written in a supported scripting language),

  • Java-based knowledge bases (i.e. written in Java).

The main advantage of scripting knowledge bases is a possibility of modification without the need of recompilation or even restarting the system.

Basic features of a knowledge base:

  • contains a logic of event processing,

  • defines callback functions that will be invoked by the Sponge engine in certain life cycles of the system (e.g. on load, on startup, on shutdown).

2.1.3. Plugins

The connection between the outside world and knowledge bases is provided by plugins.

2.1.4. Event flow

The figure below shows the event flow in the Sponge engine.

engine event flow
Figure 1. Event flow
Event flow in event processors
Filter

An event that is put into the Input Event Queue goes through defined filters. If the event hasn’t been accepted by one or more of the filters, the event is discarded.

Trigger

If there is a trigger listening for the event that successfully passed the filters, than this trigger is executed.

Rule

If there is a rule that listens to that event, the event will be processed by that rule. In this case the engine will:

  • create a new instance of this rule,

  • or save the event in the internal event tree of the already existing rule instance,

  • or cause the already existing rule instance to fire (i.e. run the rule).

Correlator

If there is a correlator that listens to that event, the event will be processed by that correlator.

2.2. Distributions

2.2.1. Embedded in a Java application

Sponge could be embedded in a Java application by adding a Maven dependency.

2.2.1.1. Maven dependency

If you want to use Sponge with, for example, Python scripting knowledge bases, add this dependency to your pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-jython</artifactId>
    <version>1.6.0</version>
</dependency>
2.2.1.2. Creating and starting the Sponge engine

The following example shows how to make Sponge a part of a custom Java application.

Example of starting the embedded Sponge with the configuration file
SpongeEngine engine = DefaultSpongeEngine.builder().config("examples/script/py/triggers_hello_world.xml").build(); (1)
engine.startup(); (2)
1 Creates a Sponge engine by using Engine Builder API and providing the Sponge XML configuration file.
2 Starts up the engine. After startup the engine runs in the background, i.e. using threads other than the current one.

The engine runs until it is shut down explicitly. So, for example, if you place this code in the main method and execute it, the program will run infinitely.

Example of starting the embedded Sponge with the knowledge base file
SpongeEngine engine = DefaultSpongeEngine.builder().knowledgeBase("knowledgeBaseName", "examples/script/py/triggers_hello_world.py").build(); (1)
engine.startup();
1 Creates a Sponge engine by using Engine Builder API providing a Python script knowledge base.

2.2.2. Standalone command-line program

Prerequisites:

  • Installed Java 1.8 or above.

  • Environment variable JAVA_HOME set or java executable placed in PATH.

Verify Java version
java -version
If necessary, logging levels could be changed in config/logback.xml. Logs will be written to the console as well as to log files placed in logs/ directory.
2.2.2.1. Linux/MacOS/Unix

First steps:

  • Unpack the archive

    unzip -q sponge-1.6.0-standalone.zip
  • Run Sponge example using a configuration file.

    cd bin
    ./sponge -c ../examples/script/py/triggers_hello_world.xml
    Output console shows
    Hello World!

    The Sponge standalone command-line application continues listening to events in an endless loop. Press CTRL+C to exit.

  • Run Sponge example using the knowledge base file

    ./sponge -k ../examples/script/py/triggers_hello_world.py

    Press CTRL+C to exit.

  • In most common situations you would run Sponge in the background

    ./sponge -k ../examples/script/py/rules_heartbeat.py &

When Sponge process is running you may send HUP signal to that process in order to reload knowledge bases.

Reloading of running knowledge bases
kill -HUP <pid>

Where <pid> is the PID of the Java executable running the Sponge. It isn’t the PID of the shell script sponge.

See User Guide for limitations of reloading knowledge bases.
Terminating the Sponge process running in the background
kill -TERM <pid>
2.2.2.2. Windows

First steps:

  • Unpack the archive

  • Run Sponge using the configuration file

    cd bin
    sponge.bat -c ..\config\py\triggers_hello_world.xml
    Output console shows
    Hello World!

    Press CTRL+C to exit the Sponge standalone command-line application.

  • Run Sponge using the knowledge base file

    sponge.bat -k ..\kb\py\triggers_hello_world.py

    Press CTRL+C to exit.

  • Run another example

    sponge.bat -k ..\kb\py\rules_heartbeat.py

    Press CTRL+C to exit.

When running on Windows, the Sponge standalone command-line program doesn’t support reloading of running knowledge bases by sending operating system signal to the background process.
2.2.2.3. Interactive mode

The Sponge standalone command-line program can be invoked in the interactive mode, providing command-line access to the knowledge base interpreter.

Invoke Sponge in the interactive mode
./sponge -k ../examples/standalone/trigger_simple.py -i
Send a new event from the console
> sponge.event("alarm").send()

The sponge variable is a facade to the Sponge engine.

Because of Sponge may print messages and exceptions to the console concurrently, the prompt could be lost in between the lines (for example in case of an exception stack trace). In that case press Enter key to make a prompt visible.
The output shows that the event has been processed by the trigger
Sound the alarm!

Multi-line statements should be entered by adding a backslash (\) to the end of all lines except the last one, e.g.:

> def printHello():\
>     print("Hello")

You may exit the program by entering exit, quit or pressing CTRL-D.

2.2.3. Docker

The standalone command-line program may also be installed as a Docker container.

Invoke bash shell in the Sponge bin directory in a Docker container
docker run -it openksavi/sponge:latest
Print Sponge help
./sponge -h
Exit the container
exit

The Docker container provides Oracle Java 8 as well as mc for convenience.

If you want to mount a host directory containing for example Sponge knowledge bases or configuration files you may use Docker volumes or mount features.

Example of mounting a host directory
docker run -it -v ~/examples:/opt/examples openksavi/sponge:latest

You may also invoke Sponge directly.

Example of invoking Sponge directly
docker run -it openksavi/sponge:latest ./sponge -c ../examples/script/py/triggers_hello_world.xml

Press CTRL+C after seeing the message "Hello World!" to exit the Sponge loop.

2.3. Examples

This chapter provides introductory examples of Sponge. For detailed information see User Guide.

Sponge is a polyglot system. It allows creating a knowledge base in one of the several supported scripting languages.

The shell commands that execute the examples require installation of the Sponge standalone command-line application and are specific to Linux/MacOS/Unix. For more information how to run the examples see the next chapter.

2.3.1. Hello World action example

Let’s start with the time-honored Hello World example. We will define a HelloWorldAction action that accepts one string argument (your name) and returns a greeting text. The same action will be implemented in different scripting languages in the following chapters.

If the Sponge REST API server is configured, you could call this action remotely.

Example of calling the action via the REST API
# Call the action to get the JSON response with the result.
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/call -d '{"name":"HelloWorldAction","args":["Sponge user"]}'

# You could also get the action metadata as a JSON response.
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/actions -d '{"name":"HelloWorldAction"}'
2.3.1.1. Python
Python Hello World action example knowledge base file
class HelloWorldAction(Action): (1)
    def onConfigure(self): (2)
        self.displayName = "Hello world" (3)
        self.description = "Returns a greeting text."
        self.argsMeta = [ArgMeta("name", StringType()).displayName("Your name").description("Type your name.")] (4)
        self.resultMeta = ResultMeta(StringType()).displayName("Greeting").description("The greeting text.") (5)
    def onCall(self, name): (6)
        return "Hello World! Hello {}!".format(name)

def onStartup(): (7)
    sponge.logger.info("{}", sponge.call("HelloWorldAction", ["Sponge user"])) (8)
1 The definition of the HelloWorldAction action.
2 The action configuration callback method. The method body defines the optional action metadata. The metadata could be used by a client code, for example a generic UI for calling actions or a REST API client.
3 Sets up the action display name and the description.
4 Sets up the action argument metadata. There is only one argument named name of String type.
5 Sets up the action result metadata.
6 The action callback method that will be invoked when the action is called.
7 The knowledge base startup function.
8 Logs the result of the action call. The first parameter is always an action name. The other parameters depend on an action definition.

The HelloWorldAction action is enabled automatically before executing onStartup(). Enabling means that an instance of HelloWorldAction class is created and then HelloWorldAction.onConfigure method is invoked to configure this action.

The full source code of the example can be found in the file actions_hello_world.py.

The onConfigure method as well as metadata are optional for actions. It is helpful only when a generic access to actions is needed or for documentation. The minimalistic version of this example that doesn’t define metadata is much simpler.

The minimalistic version of the Hello World action
class HelloWorldAction(Action):
    def onCall(self, name):
        return "Hello World! Hello {}!".format(name)
Running the example in the standalone command-line application
./sponge -k ../examples/script/py/actions_hello_world.py

Press CTRL+C to exit the Sponge standalone command-line application.

All callouts placed in the source code in the examples below remain the same, because they are functionally equivalent.
2.3.1.2. Ruby
Ruby Hello World action example knowledge base file
class HelloWorldAction < Action (1)
    def onConfigure (2)
        self.displayName = "Hello world" (3)
        self.description = "Returns a greeting text."
        self.argsMeta = [ArgMeta.new("name", StringType.new()).displayName("Your name").description("Type your name.")] (4)
        self.resultMeta = ResultMeta.new(StringType.new()).displayName("Greeting").description("The greeting text.") (5)
    end
    def onCall(name) (6)
        return "Hello World! Hello %s!" % [name]
    end
end

def onStartup (7)
    $sponge.logger.info("{}", $sponge.call("HelloWorldAction", ["Sponge user"])) (8)
end

The full source code of this example can be found in the file actions_hello_world.rb.

Running this example in the standalone command-line application
./sponge -k ../examples/script/rb/actions_hello_world.rb

Press CTRL+C to exit the Sponge standalone command-line application.

2.3.1.3. Groovy
Groovy Hello World action example knowledge base file
class HelloWorldAction extends Action { (1)
    void onConfigure() { (2)
        this.displayName = "Hello world" (3)
        this.description = "Returns a greeting text."
        this.argsMeta = [new ArgMeta("name", new StringType()).displayName("Your name").description("Type your name.")] (4)
        this.resultMeta = new ResultMeta(new StringType()).displayName("Greeting").description("The greeting text.") (5)
    }

    String onCall(String name) { (6)
        return "Hello World! Hello $name!"
    }
}

void onStartup() { (7)
    sponge.logger.info("{}", sponge.call("HelloWorldAction", ["Sponge user"])) (8)
}

The full source code of this example can be found in the file actions_hello_world.groovy.

Running this example in the standalone command-line application
./sponge -k ../examples/script/groovy/actions_hello_world.groovy

Press CTRL+C to exit the Sponge standalone command-line application.

2.3.1.4. JavaScript
JavaScript Hello World action example knowledge base file
var HelloWorldAction = Java.extend(Action, { (1)
    onConfigure: function(self) { (2)
        self.displayName = "Hello world"; (3)
        self.description = "Returns a greeting text.";
        self.argsMeta = [new ArgMeta("name", new StringType()).displayName("Your name").description("Type your name.")]; (4)
        self.resultMeta = new ResultMeta(new StringType()).displayName("Greeting").description("The greeting text."); (5)
    },
    onCall: function(self, args) { (6)
        // The onCall method in JS always gets an array of arguments. Dynamic onCall callback methods are not supported.
        return "Hello World! Hello " + args[0] + "!";
    }
});

function onStartup() { (7)
    sponge.logger.info("{}", sponge.call("HelloWorldAction", ["Sponge user"])) (8)
}

The full source code of this example can be found in the file actions_hello_world.js

Running this example in the standalone command-line application
./sponge -k ../examples/script/js/actions_hello_world.js

Press CTRL+C to exit the Sponge standalone command-line application.

2.3.2. Hello World trigger example

This chapter presents a different version of the Hello World example. In this case the text "Hello World!" will be printed when an event helloEvent fires a trigger HelloWorldTrigger.

2.3.2.1. Python
Python Hello World trigger example knowledge base file
class HelloWorldTrigger(Trigger): (1)
    def onConfigure(self): (2)
        self.event = "helloEvent" (3)
    def onRun(self, event): (4)
        print event.get("say") (5)

def onStartup(): (6)
    sponge.event("helloEvent").set("say", "Hello World!").send() (7)
1 The definition of the HelloWorldTrigger trigger.
2 The trigger configuration callback method.
3 Sets up HelloWorldTrigger to listen to helloEvent events (i.e. events that have name "helloEvent"). The event name could be also specified as a regular expression. For example "helloEvent.*" would configure this trigger to listen to all events whose name starts with "helloEvent".
4 The trigger onRun method will be called when an event helloEvent happens. The event argument is a reference to the event instance.
5 Prints the value of the event attribute "say".
6 The knowledge base startup function.
7 Send a new event helloEvent that has an attribute "say" with the text value "Hello World!".

The trigger HelloWorldTrigger is enabled automatically before executing onStartup(). Enabling means that an instance of HelloWorldTrigger class is created and then HelloWorldTrigger.onConfigure method is invoked to configure this trigger.

The full source code of this example can be found in the file triggers_hello_world.py.

Running this example in the standalone command-line application
./sponge -k ../examples/script/py/triggers_hello_world.py
The output console shows
Hello World!

Press CTRL+C to exit the Sponge standalone command-line application.

All callouts placed in the source code in the examples below remain the same, because they are functionally equivalent.
2.3.2.2. Ruby
Ruby Hello World trigger example knowledge base file
class HelloWorldTrigger < Trigger (1)
    def onConfigure (2)
        self.event = "helloEvent" (3)
    end

    def onRun(event) (4)
        puts event.get("say") (5)
    end
end

def onStartup (6)
    $sponge.event("helloEvent").set("say", "Hello World!").send() (7)
end

The full source code of this example can be found in the file triggers_hello_world.rb.

Running this example in the standalone command-line application
./sponge -k ../examples/script/rb/triggers_hello_world.rb

Press CTRL+C to exit.

2.3.2.3. Groovy
Groovy Hello World trigger example knowledge base file
class HelloWorldTrigger extends Trigger { (1)
    void onConfigure() { (2)
        this.event = "helloEvent" (3)
    }
    void onRun(Event event) { (4)
        println event.get("say") (5)
    }
}

void onStartup() { (6)
    sponge.event("helloEvent").set("say", "Hello World!").send() (7)
}

The full source code of this example can be found in the file triggers_hello_world.groovy.

Running this example in the standalone command-line application
./sponge -k ../examples/script/groovy/triggers_hello_world.groovy

Press CTRL+C to exit.

2.3.2.4. JavaScript
JavaScript Hello World trigger example knowledge base file
var HelloWorldTrigger = Java.extend(Trigger, { (1)
    onConfigure: function(self) { (2)
        self.event = "helloEvent"; (3)
    },
    onRun: function(self, event) { (4)
        print(event.get("say")); (5)
    }
});

function onStartup() { (6)
    sponge.event("helloEvent").set("say", "Hello World!").send(); (7)
}

The full source code of this example can be found in the file triggers_hello_world.js

Running this example in the standalone command-line application
./sponge -k ../examples/script/js/triggers_hello_world.js

Press CTRL+C to exit.

2.3.3. Heartbeat rule example

This example presents a more advanced use case of Sponge.

The rule HeartbeatRule will fire (i.e. execute its onRun method) when it detects a time gap between heartbeat events that is longer than 2 seconds. This scenario could be used in a monitoring system to verify that a particular service is running.

2.3.3.1. Python
Python Heartbeat example knowledge base file
# Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule(Rule): (1)
    def onConfigure(self): (2)
        self.events = ["heartbeat h1", "heartbeat h2 :none"] (3)
        self.addConditions("h2", lambda rule, event: rule.firstEvent.get("source") == event.get("source")) (4)
        self.duration = Duration.ofSeconds(2) (5)
    def onRun(self, event): (6)
        sponge.event("alarm").set("severity", 1).send() (7)

class AlarmTrigger(Trigger): (8)
    def onConfigure(self):
        self.event = "alarm"
    def onRun(self, event):
        print "Sound the alarm!"
1 The definition of the rule HeartbeatRule.
2 Rule configuration method.
3 Setup HeartbeatRule to listen to heartbeat events (i.e. events that have name "heartbeat") and detect a situation that when heartbeat event happens, then there will be no new heartbeat event for 2 seconds. So it detects a time gap between heartbeat events. To first occurrence of event heartbeat is assigned an alias h1, to the next h2. They are required because the same event type is used more than once. :none sets an event mode for the second occurrence of heartbeat that tells that there should happen no such event.
4 Add the event condition for the event h2 that correlates events that have the same source (specified as an event attribute). The rule.firstEvent property is a reference to the first event accepted by this rule (in this case h1).
5 Set a duration of this rule to 2 seconds. After that time (counting since the occurrence of h1) the state of the rule will be verified and if the specified situation happens, the rule will fire.
6 The onRun method will be called when a specified situation takes place. The event argument is a reference to the last event in the sequence, so in this case it is null because there is no second event. The complete sequence of events will be returned by the method getEventSequence(). A single event instance is returned by the method getEvent(eventAlias).
7 Send a new alarm event that will be processed on a more abstract level.
8 A trigger that listens to alarm events and prints that the alarm has been activated. In the real use case the rule could, for example, send an email or SMS.

The full source code of this example can be found in the file rules_heartbeat.py.

Running this example in the standalone command-line application
./sponge -k ../examples/script/py/rules_heartbeat.py
After a few seconds the output console shows
Sound the alarm!

Press CTRL+C to exit the Sponge standalone command-line application.

This example doesn’t detect a situation when there hasn’t been any heartbeat event since the startup of the Sponge. To remedy that issue you could use the startup event. See the chapter on Startup system event in the User Guide.
2.3.3.2. Ruby
Ruby Heartbeat example knowledge base file
# Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule < Rule (1)
    def onConfigure (2)
        self.events = ["heartbeat h1", "heartbeat h2 :none"] (3)
        self.addConditions("h2", lambda { |rule, event|
            return rule.firstEvent.get("source") == event.get("source")
        }) (4)
        self.duration = Duration.ofSeconds(2) (5)
    end
    def onRun(event) (6)
        $sponge.event("alarm").set("severity", 1).send() (7)
    end
end

class AlarmTrigger < Trigger (8)
    def onConfigure
        self.event = "alarm"
    end
    def onRun(event)
        puts "Sound the alarm!"
    end
end

The full source code of this example can be found in the file rules_heartbeat.rb.

Running this example in the standalone command-line application
./sponge -k ../examples/script/rb/rules_heartbeat.rb
After a few seconds the output console shows
Sound the alarm!

Press CTRL+C to exit.

2.3.3.3. Groovy
Groovy Heartbeat example knowledge base file
// Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule extends Rule { (1)
    void onConfigure() { (2)
        this.events = ["heartbeat h1", "heartbeat h2 :none"] (3)
        this.addConditions("h2", { rule, event ->
            return rule.firstEvent.get("source") == event.get("source")
        }) (4)
        this.duration = Duration.ofSeconds(2) (5)
    }
    void onRun(Event event) { (6)
        sponge.event("alarm").set("severity", 1).send() (7)
    }
}

class AlarmTrigger extends Trigger { (8)
    void onConfigure() {
        this.event = "alarm"
    }
    void onRun(Event event) {
        println "Sound the alarm!"
    }
}

The full source code of this example can be found in the file rules_heartbeat.groovy.

Running this example in the standalone command-line application
./sponge -k ../examples/script/groovy/rules_heartbeat.groovy
After a few seconds the output console shows
Sound the alarm!

Press CTRL+C to exit.

2.3.3.4. JavaScript
JavaScript Heartbeat example knowledge base file
// Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
var HeartbeatRule = Java.extend(Rule, { (1)
    onConfigure: function(self) { (2)
        self.events = ["heartbeat h1", "heartbeat h2 :none"]; (3)
        self.addConditions("h2", function(rule, event) {
            return rule.firstEvent.get("source") == event.get("source");
        }); (4)
        self.duration = Duration.ofSeconds(2); (5)
    },
    onRun: function(self, event) { (6)
        sponge.event("alarm").set("severity", 1).send(); (7)
    }
});

var AlarmTrigger = Java.extend(Trigger, { (8)
    onConfigure: function(self) {
        self.event = "alarm";
    },
    onRun: function(self, event) {
        print("Sound the alarm!");
    }
});

The full source code of this example can be found in the file rules_heartbeat.js.

Running this example in the standalone command-line application
./sponge -k ../examples/script/js/rules_heartbeat.js
After a few seconds the output console shows
Sound the alarm!

Press CTRL+C to exit.

3. User Guide

3.1. Introduction

Sponge is a polyglot system that allows creating knowledge bases in several scripting languages.

For the purpose of clarity, examples in this chapter are written in Python (Jython) as one of the supported scripting languages and in Java. All examples written in Python may have equivalent ones written in any of the other supported script languages.

3.2. Architecture

The figure below presents the architecture of the Sponge system.

engine architecture
Figure 2. Architecture

The Sponge engine consists of the following components.

Table 1. Engine components
Engine component Description

Configuration Manager

The module providing an access to a configuration.

Plugin Manager

The module that manages plugins.

Knowledge Base Manager

The module that for manages knowledge bases.

Event Scheduler

The scheduler of future events that are to be added into the Input Event Queue.

Input Event Queue

The input queue of events that are sent to Sponge. Events can get to this queue form different sources: plugins, Event Scheduler or knowledge bases.

Filter Processing Unit

The module providing the filtering of events. It is also a registry of enabled filters.

Main Event Queue

The queue of events that passed all filters and are to be processes by other event processors in the Main Processing Unit.

Main Processing Unit

The module that manages the processing of events by triggers, rules and correlators. It is also a registry of such event processors.

Output Event Queue

The queue of ignored events, i.e. events that haven’t been listened to by any trigger, rule or correlator. Events rejected by filters don’t go to the Output Event Queue. The default behavior is to log and forget ignored events.

Processor Manager

The module responsible for enabling and disabling processors, i.e. actions, filters, triggers, rules and correlators.

Action Manager

The registry of enabled actions.

Thread Pool Manager

The module responsible for thread pool management.

3.3. Configuration

Sponge can be configured:

  • in an XML configuration file,

  • using the Engine Builder API.

3.3.1. XML configuration file

In a general form an XML configuration file is built as in the following example:

Example XML configuration file
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <!-- Properties configuration section -->
    <properties>
        <property name="sponge.home" system="true">.</property>
        <property name="server.name">sponge.openksavi.org</property>
        <property name="filterThreshold" variable="true">10</property>
    </properties>

    <!-- Engine configuration section -->
    <engine name="SampleSponge" displayName="Sample Sponge">
        <description>The sample Sponge engine.</description>
        <mainProcessingUnitThreadCount>6</mainProcessingUnitThreadCount>
    </engine>

    <!-- Knowledge bases configuration section -->
    <knowledgeBases>
        <knowledgeBase name="pythonKb" displayName="Python-based processors">
            <description>The Python-based knowledge base.</description>
            <file>kb_file_1.py</file>
            <file required="false">${sponge.home}/examples/script/py/kb_file_2.py</file>
            <file>${sponge.configDir}/kb_file_3.py</file>
        </knowledgeBase>
        <knowledgeBase name="kotlinKb" class="org.openksavi.sponge.kotlin.examples.HelloWorld" />
    </knowledgeBases>

    <!-- Plugins configuration section -->
    <plugins>
        <plugin name="connectionPlugin" class="org.openksavi.sponge.examples.ConnectionPlugin" displayName="Connection plugin">
            <description>The connection plugin provides the connection related code.</description>
            <configuration>
                <connection>
                    <name>Example connection</name>
                </connection>
            </configuration>
        </plugin>
    </plugins>
</sponge>

The specification of a configuration XML file is provided by the schema file config.xsd.

The configuration file is looked up using the default strategy provided by Apache Commons Configuration, e.g. first in the file system as a relative or absolute path and then in the classpath. If not found, the configuration file is looked up in the file system relative to the Sponge home directory.

3.3.1.1. Properties configuration

The properties configuration section (properties) allows setting configuration properties. Configuration properties may be used in other places in the configuration file. Moreover the properties can be used as Java system properties if the attribute system is set to true. Java system properties passed to the JVM take precedence over the ones defined in the properties configuration section of the configuration file. So, for example passing -Dsponge.home=/opt/sponge to the JVM will override the corresponding property configuration.

Properties can also be used as Sponge variables in the engine scope. In that case you have to set the attribute variable to true. The type of such variables is always String.

Table 2. Predefined properties
Property Description

sponge.home

The Sponge home directory. The Sponge home directory may also be provided as a Java system property sponge.home.

sponge.configDir

The configuration file directory. This property is read only. It may be null if there is no Sponge configuration file.

Properties may be loaded from an optional properties file located in the same directory as the configuration file, which name follows the convention <XML configuration file basename>.properties. This properties file is supposed to be encoded in UTF-8.

3.3.1.2. Engine configuration

The engine configuration section (<engine>) contains engine configuration parameters and an optional name of the Sponge engine. The most important parameters are mainProcessingUnitThreadCount, asyncEventSetProcessorExecutorThreadCount and eventQueueCapacity.

Table 3. Engine parameters
Parameter Description Default value

mainProcessingUnitThreadCount

The number of Main Processing Unit worker threads. You could increase this parameter if your knowledge bases require concurrent processing of many events by triggers, rules or correlators.

10

asyncEventSetProcessorExecutorThreadCount

The number of threads used by an event set processor thread pool (executor) for asynchronous processing of events (by rules and correlators). You could increase this parameter if your knowledge bases create many instances of asynchronous rules or correlators. In such case, for better performance, this parameter should be equal to or greater than mainProcessingUnitThreadCount.

10

eventQueueCapacity

The capacity (i.e. maximum size) of the Input Event Queue. Value -1 means infinity. You should set eventQueueCapacity to a reasonable value taking into account the amount and intensity of events your system needs to process, the typical event size, the number of threads in thread pools, etc. When the system is overloaded with events and this parameter is set to the value other than infinity, every attempt to send a new event will throw QueueFullException. However, when the system is overloaded and this parameter is to too high or infinite, you risk OutOfMemoryError.

-1

eventClonePolicy

Event clone policy (shallow or deep).

shallow

autoEnable

Auto-enable script-based processors.

true

durationThreadCount

The number of threads used by the event set processors duration thread pool (executor). The default implementation uses these threads only to send control events. In most cases there should be no need to change this parameter, because sending a new event is relatively fast.

2

eventSetProcessorDefaultSynchronous

The event set processor default synchronous flag. If this parameter is set to true then all rules and correlators that have no synchronous flag specified in their configuration would be assumed as synchronous. If an event set processor is synchronous it means that an event will be processed sequentially (in one thread) for all instances of this event set processor. If an event set processor is asynchronous then an event will be processed by the instances of this event set processor concurrently (in many threads). The default behavior is asynchronous. In most cases you wouldn’t need to change this parameter.

false

executorShutdownTimeout

The thread pool (executor) shutdown timeout (in milliseconds). You could, for example, increase this parameter to guarantee a graceful shutdown if event processors need more time to finish processing when the engine is shutting down. The actual shutting down of the entire engine may take longer than executorShutdownTimeout because this parameter is applied separately to several thread pools in the engine.

60000

3.3.1.3. Knowledge bases configuration

The knowledge bases configuration section (<knowledgeBases>) lists all script knowledge bases that are to be loaded into the engine.

Each <knowledgeBase> tag contains:

Table 4. Knowledge base configuration
Tag Type Description

name

Attribute

The name of the knowledge base.

displayName

Attribute

The display name of the knowledge base.

type

Attribute

The type of the script knowledge base corresponding to the scripting language. Allowed values: python, ruby, groovy, javascript. The type is required only for knowledge bases that specify no files so their type can’t be inferred from the file extensions.

class

Attribute

The class of the non script knowledge base. In that case you don’t have to specify a type and you must not specify files. A knowledge base class should define a non-parameterized constructor.

description

Element

The description of the knowledge base.

file

Element

The file name of the knowledge base. A single knowledge base may use many files but all of them have to be written in one language.

The file element may have the following optional attributes.

  • charset - sets the file encoding.

  • required - if set to false, the non existing files are ignored. The default value is true so when the file doesn’t exist, the exception is thrown.

3.3.1.4. Plugins configuration

The plugins configuration section (<plugins>) contains plugin definitions (<plugin>) built as follows:

Table 5. Plugin configuration attributes
Tag Type Description

name

Attribute

The unique name of the plugin (mandatory). A text without white spaces and special symbols. Also used as a variable name in order to access a given plugin in the knowledge base.

displayName

Attribute

The plugin display name.

class

Attribute

The name of the plugin class (Java class or a class defined in the scripting language in the script knowledge base (mandatory).

knowledgeBaseName

Attribute

The name of the knowledge base containing the class of the plugin (optional). If not set then the default Java-based knowledge base is used.

description

Element

The plugin description.

configuration

Element

The specific configuration of the plugin.

You may provide a custom plugin configuration section inside a <configuration> element. The contents of this plugin configuration depend on the given plugin implementation. Usually it would be a hierarchy of plugin specific sub tags.

3.3.2. Engine Builder API

The Engine Builder API is provided by DefaultSpongeEngine.builder() static method that returns the EngineBuilder instance. This API follows a builder design pattern.

Example configuration using the Engine Builder API
EchoPlugin plugin = new EchoPlugin();
plugin.setName("testPlugin");
plugin.setEcho("Echo text!");

SpongeEngine engine = DefaultSpongeEngine.builder()
        .systemProperty("sponge.home", "..")
        .property("test.property", "TEST")
        .plugin(plugin)
        .knowledgeBase("helloWorldKb", "examples/script/py/triggers_hello_world.py")
        .knowledgeBase(new TestKnowledgeBase())
        .build();

engine.getConfigurationManager().setMainProcessingUnitThreadCount(25);
engine.getConfigurationManager().setEventClonePolicy(EventClonePolicy.DEEP);

engine.startup();

The Engine Builder API provides the method config() to read an XML configuration file as well.

Example of using the XML configuration file in the Engine Builder API
SpongeEngine engine = DefaultSpongeEngine.builder().config("examples/core/engine_parameters.xml").build();
engine.startup();

The Engine Builder API preserves the load order of knowledge bases, including knowledge bases specified in the configuration file.

You may set engine parameters via ConfigurationManager but only after invoking build() and before starting up the engine.

3.4. Engine

3.4.1. Starting up

To startup the engine you should invoke the startup() method. After startup, the engine runs in the background (i.e. using threads other than the current one) until you shutdown it.

Example of starting up
SpongeEngine engine = DefaultSpongeEngine.builder().config("examples/script/py/triggers_hello_world.xml").build();
engine.startup();

3.4.2. Shutting down

When a Sponge instance is no longer needed it should be shut down by invoking shutdown() or requestShutdown() method. It instructs the engine to do some clean up, stop all managed threads, free resources, etc. The shutdown() uses the current thread to stop the engine. The requestShutdown() uses a new thread to stop the engine, thus allowing to shutdown the engine from the within, e.g. form an event processor.

Example of shutting down in Java
engine.shutdown();
Example of shutting down in a script language
class SomeTrigger(Trigger):
    def onConfigure(self):
        self.event = "e1"
    def onRun(self, event):
        sponge.requestShutdown()

Shutting down doesn’t guarantee that all events sent to the engine will be processed. However, all events that have already been read from the Input Event Queue (by the Filter Processing Unit) will be fully processed by the engine, if the processing doesn’t exceed shutdown timeouts (specified by the executorShutdownTimeout configuration parameter). All newer events remaining in the Input Event Queue will not be processed at all.

3.5. Knowledge bases

A knowledge base is used mainly to define processors. A knowledge base may be written in one of the supported scripting languages. An alternative way of defining knowledge bases is to write them directly in Java or Kotlin. However, using a scripting knowledge base has advantages such as that a modified script knowledge base doesn’t need recompilation.

There is a global namespace for all processors, regardless of the knowledge base they are defined in. When there is more than one processor of the same name in the engine, only the last enabled one will be registered. However, you can’t enable a processor if an another one that has the same name and is of a different type has already been enabled.

Script knowledge base files are looked up in the file system as a relative or absolute path, then in the classpath, then in the file system relative to the XML configuration file parent directory and then in the file system relative to the Sponge home directory. A knowledge base file name may contain wildcards (for files only, not directories), according to the glob pattern. Wildcards are not supported for classpath resources. This default behavior can be changed by providing a custom implementation of KnowledgeBaseFileProvider and passing it to the setKnowledgeBaseFileProvider method on the engine. For example the SpringEngineBuilder uses the SpringKnowledgeBaseFileProvider that supports the Spring classpath*: notation for loading resources.

The order of loading knowledge bases preserves the order specified in the configuration. Likewise the order of loading files of the same knowledge base preserves the order specified in the configuration.

Scripting knowledge bases are read by interpreters. For every knowledge base there is one instance of an interpreter.

3.5.1. Knowledge base file structure

Generally, script knowledge base files consist of a few parts:

  1. Import of modules and packages (from the scripting language or Java).

  2. Definitions of knowledge base processors (actions, filters, triggers, rules and correlators).

  3. Definitions of callback functions that will be invoked in particular situations:

    Table 6. Callback functions
    Function Description

    onInit()

    Called once on the initialization of a knowledge base.

    onLoad()

    Called on the loading of a knowledge base and also every time a knowledge base is reloaded. Before invoking an onLoad callback method, the engine scans to auto-enable processors (if this functionality is turned on). You may manually disable some of the auto enabled processors in this callback function if necessary.

    onStartup()

    Called once after the startup of the engine (after onInit() and initial onLoad()).

    boolean onRun()

    Called just after an onStartup() callback function. If this function returns true for every knowledge base, then the engine will start its threads and perform an endless loop in order to process events. This is the default behavior. Otherwise, the engine will assume a run once mode and it will invoke a shutdown without starting an event processing. The latter option allows a user to, for example, just run a script and stop the engine. It could be useful when using a standalone application to perform simple, synchronized tasks. In case of scripting knowledge bases onRun() may return null which will be treated as if it returned true.

    onShutdown()

    Called once before the shutdown of the engine.

    onBeforeReload()

    Called before every reloading of the knowledge base.

    onAfterReload()

    Called after every reloading of the knowledge base.

Sponge follows a convention that the names of all callback functions and methods start with on, e.g. onStartup for a knowledge base or onConfigure for a processor.

You shouldn’t place more than one callback function that has the same name in the same knowledge base (event in different files of that knowledge base). If there is more than one callback function that has the same name in the same knowledge base only the last loaded function will be invoked. Furthermore it could depend on the specific scripting language.

When Sponge is starting, callback functions are invoked in the following order:

  1. executing all knowledge base files as scripts, i.e. executing the main body of the script files,

  2. onInit(),

  3. onLoad(),

  4. onStartup(),

  5. onRun().

Before onStartup() is invoked you will not be able to send events or access plugins. That is because the engine hasn’t started fully yet.

When a knowledge base is reloaded, the callback functions are invoked in the following order:

  1. onBeforeReload() executed in the previous version of the reloaded knowledge base,

  2. executing all knowledge base files as scripts, i.e. executing the main body of the script files,

  3. onLoad() executed in the new version of the reloaded knowledge base,

  4. onAfterReload() executed in the new version of the reloaded knowledge base.

3.5.2. Global variables

The following predefined global variables are available in all knowledge bases.

Table 7. Global variables
Global variable Description

sponge

The facade to the Sponge engine operations that provides methods to send events, manually enable or disable event processors etc. This variable represents an instance of a Java class implementing org.openksavi.sponge.kb.KnowledgeBaseEngineOperations.

For each plugin a global variable will be created. The name of this variable is the plugin name (i.e. the value configured as the plugin name attribute in the configuration).

3.5.2.1. Engine facade
Table 8. Important engine facade properties and methods
Property / Method Description

kb

The knowledge base to which belongs the script using this variable. This value represents an object of a Java class implementing org.openksavi.sponge.kb.KnowledgeBase (for script knowledge base it is org.openksavi.sponge.kb.ScriptKnowledgeBase).

interpreter

The knowledge base interpreter that has read the script using this variable. Generally it is an implementation of org.openksavi.sponge.kb.KnowledgeBaseInterpreter. In the case of a scripting knowledge base it returns an implementation of org.openksavi.sponge.kb.ScriptKnowledgeBaseInterpreter.

engine

The engine. This is the reference to the actual implementation of the SpongeEngine interface.

logger

The logger instance associated with the knowledge base. The name of this logger has the following format: sponge.kb.<language>.<knowledgeBaseName>.global, e.g. sponge.kb.python.kb1.global. Please note, that event processors and plugins have their own loggers (they are referenced as self.logger).

enable()

Enables the processor.

enableAll()

Enables processors.

disable()

Disables the processor.

disableAll()

Disables processors.

enableJava()

Enables the Java-based processor.

enableJavaAll()

Enables Java-based processors.

disableJava()

Disables the Java-based processor.

disableJavaAll()

Disables Java-based processors.

Object call(String actionName, Object…​ args)

Calls registered action with arguments.

shutdown()

Shuts down the engine using the current thread.

requestShutdown()

Shuts down the engine using another thread.

reload()

Reloads script-based knowledge bases.

requestReload()

Reloads script-based knowledge bases using another thread.

boolean removeEvent(EventSchedulerEntry entry)

Removes the scheduled event.

getPlugin(String name)

Returns the plugin that has the specified name or null if there is no such plugin.

getPlugin(Class<T> cls, String name)

Returns the plugin that has the specified name and type or null if there is no such plugin.

getPlugin(Class<T> cls)

Returns the plugin that has the specified type or null if there is no such plugin.

EventDefinition event(String name)

Creates a new event definition.

EventDefinition event(String name, EventClonePolicy policy)

Creates a new event definition.

EventDefinition event(Event event)

Creates a new event definition.

boolean exists<Processor>(String name)

A set of methods returning true if a processor named name exists. The actual methods are: existsFilter, existsTrigger, existsRule, existsCorrelator, existsAction.

setVariable(String name, Object value)

Sets the engine scope variable.

Object getVariable(String name)

Returns the value of the engine scope variable. Throws exception if not found.

T getVariable(Class<T> cls, String name)

Returns the value of the engine scope variable. Throws exception if not found.

T getVariable(String name, T defaultValue)

Returns the value of the engine scope variable or defaultValue if not found.

T getVariable(Class<T> cls, String name, T defaultValue)

Returns the value of the engine scope variable or defaultValue if not found.

removeVariable(String name)

Removes the engine scope variable.

boolean existsVariable(String name)

Returns true if the engine scope variable named name exists.

setVariableIfNone(String name, Supplier<T> supplier)

Sets the engine scope variable if not set already.

version

The read-only property whose value is the engine version.

description

The read-only property whose value is the engine description.

statisticsSummary

The read-only property whose value is the engine statistics summary as a text.

3.5.3. User variables

A user variable could be defined in one of the two scopes:

  • the engine scope,

  • the knowledge base scope.

3.5.3.1. Engine scope

The engine scope variables could be accessed in any knowledge base.

The engine scope variable examples
sponge.setVariable("soundTheAlarm", AtomicBoolean(False))
sponge.getVariable("soundTheAlarm").set(True)
The engine scope is the same as a Sponge internal session scope. This is because currently there is only one session per a single Sponge engine instance.
3.5.3.2. Knowledge base scope

The knowledge base scope variables may be accessed only in the knowledge base they are defined in.

The knowledge base scope variable examples
hearbeatEventEntry = None

def onStartup():
    global hearbeatEventEntry
    hearbeatEventEntry = sponge.event("heartbeat").sendAfter(100, 1000)

3.5.4. Loading knowledge base from an additional file

Sponge gives the possibility to define a knowledge base in a few files. In order to do that, in the configuration file in the <engine> section you may define which files should be loaded by adding <file> tags to <knowledgeBase>. Additional files could also be loaded from a knowledge base level.

sponge.kb.load("triggers.py")

When the same name is used for a new processor, the previous definition will be replaced with the new one. However, this behavior could depend on the specific scripting language.

3.5.5. Reloading

Sometimes a situation may happen that there will be a need for a dynamic modification of processors, for example to add a new rule or modify an existing one. It is possible to do it without the need of shutting down and then starting the system again.

When variables are used in a knowledge base and you don’t want them to be changed after reloading of the knowledge base, you should place their definitions in onInit() callback functions rather than simply in the main script or in onLoad(). That is because the main script and onLoad() are always executed during reloading but onInit() function is not.

When reloading the system, the configuration file is not loaded again. If the changes in this file (e.g. registering a new plugin) are to be visible in the system, the only way is to restart.

When the Sponge engine is being reloaded, the previously defined processors will not be removed from the registry. When a processor definition has changed in the file being reloaded, it will be auto-enabled (i.e. registered) once more with the new definition. If auto-enable is off, then sponge.enable method must be invoked. In that case sponge.enable should be placed in the onLoad() callback function.

If auto-enable is on (this is the default setting), then all processors will be enabled after reloading, even processors that have been manually disabled before.

There is a limitation in reloading a knowledge base that defines event set processors (i.e. rules or correlators). When there are existing instances of event set processors, they will be dismissed.

Depending on the specific interactions and taking into account differences in the third-party implementations of scripting languages, reloading sometimes may lead to problems or side effects and it should be used carefully. For example if onLoad callback function definition is removed in the Python script file before reloading, the instance of this function that had been loaded before will still be present in the interpreter and will be invoked. That is because the scripts being reloaded run in the same interpreter instance.

3.5.6. Use of many knowledge base files

As mentioned before, Sponge provides the possibility to read a knowledge base from many files. Dividing a knowledge base into a few files allows in an easy way to separate some functionalities.

The order in which the files are loaded is important. The files will be loaded in such order in which they were placed in the configuration.

3.5.7. Synchronization of processes in a knowledge base

Sponge is a multi-threaded system. Sponge engine operations are thread-safe. However, attention should be paid that processors defined in a knowledge base access any shared resources in a thread-safe way. This could be achieved in various ways using Java or scripting language mechanisms.

3.5.8. Non script knowledge bases

Non script knowledge bases may be written in Java or Kotlin. Non script base processor classes follow the naming convention JAction, JTrigger, JKnowledgeBase etc for Java and KAction, KTrigger, KKnowledgeBase etc for Kotlin.

3.5.8.1. Java knowledge bases
Example of sending events from within a Java knowledge base
public class TestKnowledgeBase extends JKnowledgeBase { (1)

    public static class TestTrigger extends JTrigger { (2)

        @Override
        public void onConfigure() {
            setEvent("e1");
        }

        @Override
        public void onRun(Event event) {
            getLogger().debug("Run");
        }
    }

    @Override
    public void onStartup() {
        getSponge().event("e1").set("mark", 1).sendAfter(1000); (3)
    }
}
1 The definition of the Java-based knowledge base class.
2 The definition of the Java trigger.
3 Makes an event of type (name) e1 with an attribute mark set to 1 and schedules it to be sent after 1 second.
3.5.8.1.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-core</artifactId>
    <version>1.6.0</version>
</dependency>
3.5.8.2. Kotlin knowledge bases

Kotlin-based knowledge bases are currently supported only as non script knowledge bases.

Example of a Kotlin knowledge base
class Filters : KKnowledgeBase() {

    class ColorFilter : KFilter() {
        override fun onConfigure() = setEvent("e1")
        override fun onAccept(event: Event): Boolean {
            logger.debug("Received event {}", event)
            val color: String? = event.get("color", null)
            if (color == null || color != "blue") {
                logger.debug("rejected")
                return false
            } else {
                logger.debug("accepted")
                return true
            }
        }
    }

    class ColorTrigger : KTrigger() {
        override fun onConfigure() = setEvent("e1")
        override fun onRun(event: Event) {
            logger.debug("Received event {}", event)
        }
    }

    override fun onStartup() {
        sponge.event("e1").send()
        sponge.event("e1").set("color", "red").send()
        sponge.event("e1").set("color", "blue").send()
    }
}

In Kotlin knowledge bases there is no global variable sponge. Instead you have to use the sponge property.

See more examples of Kotlin-based knowledge bases in the sponge-kotlin project.

3.5.8.2.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-kotlin</artifactId>
    <version>1.6.0</version>
</dependency>

3.5.9. Scripting knowledge bases interoperability

There are some limitation in the interoperability between scripting knowledge bases:

  • You shouldn’t pass knowledge base interpreter scope variables from one knowledge base to an another. Even if they are written in the same scripting language. This is because each knowledge base has its own instance of an interpreter.

  • Data structures used for communicating between different knowledge bases should by rather Java types or simple types that would be handled smoothly by Java implementations of scripting languages. For example you shouldn’t use a script-based plugin in knowledge bases other than the one in which this plugin has been defined.

  • Using more than one knowledge base written in the same scripting language may, in certain situations, also cause problems, due to the internal implementations of scripting language interpreters.

3.5.10. Useful knowledge base commands

Make and send a new event.
sponge.event("alarm").set("severity", 10).send()
Print registered (i.e. enabled) triggers.
print sponge.engine.triggers
Print registered rule groups.
print sponge.engine.ruleGroups
Print instances of the first rule group.
print sponge.engine.ruleGroups[0].rules
Print registered correlator groups.
print sponge.engine.correlatorGroups
Shutdown using a new thread.
sponge.requestShutdown()
Print the engine statistics summary.
print sponge.engine.statisticsManager.summary

For more information see Sponge Javadoc.

3.5.11. Predefined knowledge base libraries

Sponge provides a few predefined script files that may be used as one of files in your compatible (i.e. written in the same language) knowledge bases. For example you may use the Jython library in your XML configuration file: <file>classpath*:org/openksavi/sponge/jython/jython_library.py</file>. The classpath* notation is available only for Spring aware engines and allows to use Ant style (*) specifications for directories and files.

3.5.12. Knowledge base versioning

You may specify a version number for a knowledge base as an integer. It could be useful for example to enforce version checking when calling actions via the REST API. You should set the version in the onLoad callback function. After editing the knowledge base file and before reloading the engine, you could increase the version number.

Example of setting the knowledge base version.
def onLoad():
    sponge.kb.version = 1

3.6. Types

Types are represented by classes in the org.openksavi.sponge.type package. In order to provide more flexibility in a client code a type specification may be extended by basic properties. Types are used for example in action metadata.

Table 9. Basic type properties
Property Description

nullable

Tells if a value of this type can be null. The default is that a value must not be null, i.e. it is not nullable.

format

An optional format.

defaultValue

An optional default value.

features (or feature)

Optional features as a map of names to values (as the Object type).

Table 10. Supported types
Type Description

StringType

A string type. Provides optional properties minLength and maxLength, e.g.: StringType().maxLength(10).

NumberType

A number type, that include both integer and floating-point numbers. Provides optional properties minValue, maxValue, exclusiveMin and exclusiveMax, e.g.: NumberType().

IntegerType

An integer type (commonly used integer type or long). Provides optional properties minValue, maxValue, exclusiveMin and exclusiveMax, e.g.: IntegerType().minValue(1).minValue(100).

BooleanType

A boolean type.

BinaryType

A binary (byte array) type. Provides an optional property mimeType.

AnyType

An any type. It may be used in situations when type is not important.

VoidType

A void type that may be used to specify that an action returns no meaningful result.

ObjectType

An object. This type requires a class name (typically a Java class name) as a constructor parameter. For example: ObjectType("org.openksavi.sponge.examples.CustomObject"). It also supports an array notation: ObjectType("org.openksavi.sponge.examples.CustomObject[]").

ListType

A list type. This type requires a DataType parameter, which is is a type of list elements. For example: ListType(ObjectType("org.openksavi.sponge.examples.CustomObject")).

MapType

A map type. This type requires two DataType parameters: a type of keys and a type of values in the map. For example: MapType(StringType(), ObjectType("org.openksavi.sponge.examples.CustomObject")).

AnnotatedType

An annotated type. This type requires a DataType parameter, which is is a type of an annotated value. For example: AnnotatedType(BinaryType().mimeType("image/png")). A value of this type should be an instance of the AnnotatedValue class, e.g. AnnotatedValue(imageBytes, {"filename":imageFilename}), where the first parameter is the annotated value and the second is the features map that may be used in a client code.

Type examples
StringType().maxLength(10).format("ipAddress")
IntegerType().minValue(1).maxValue(100).defaultValue(50)
AnyType().nullable(True)
ListType(StringType())
ListType(ObjectType("java.math.BigDecimal"))
ObjectType("java.lang.String[]")
ObjectType("org.openksavi.sponge.examples.CustomObject")
ListType(ObjectType("org.openksavi.sponge.examples.CustomObject"))
BinaryType().mimeType("image/png").features({"width":28, "height":28, "color":"white"})

3.7. Events

Events are the basic element of processing in Sponge. They have properties such as id, name and send time. The name of an event is also the type of this event. All events than have the same name belong to the same type. Event names should follow Java naming conventions for variable names. Events may have any number of attributes. These attributes will be available, for example, in event processors.

Sponge supports only point-in-time events.

3.7.1. Properties and methods

Table 11. Event properties and methods
Property / Method Description

id

A property that is a global unique identifier of an event (String). This is a shortcut for getId()/setId(text) methods.

name

A read-only property that is the name (type) of an event. This is a shortcut for the getName() method. A name of an event shouldn’t be changed after the event has been created. A name must not be empty nor contain white spaces or reserved characters (:). You should also avoid using names that are regular expressions.

time

A property that is a send time of an event, i.e. a time of adding an event to the Input Event Queue. The time is represented as java.time.Instant. This is a shortcut for getTime()/setTime(instant) methods.

set(attributeName, value)

A method that allows setting an attribute of an event.

Object get(attributeName)

A method that returns a value of an attribute or throws IllegalArgumentException if it does’t exist.

Object get(attributeClass, attributeName)

A method that returns a value of an attribute (assuming it is an instance of attributeClass) or throws IllegalArgumentException if it does’t exist.

Object get(attributeName, defaultValue)

A method that returns a value of an attribute or defaultValue if it does’t exist.

boolean has(attributeName)

A method that checks if an event has an attribute.

all

A property that returns a map of all attributes. This is a shortcut for the getAll() method.

Event clone()

A method that clones an event.

Properties id and time are automatically set when adding an event to the Input Event Queue and there is no need for setting them manually.

3.7.2. Typical event processing

In order to process an event there must be an event processor listening to events of that type (the types of events are recognized by their names). So this steps should be taken:

  • Creating an event processor.

  • Enabling the event processor automatically or manually (by invoking a proper sponge.enable*() method).

  • Creating a new event instance and sending it to the system (e.g. sponge.event("alarm").set("location", "Building 1").send().

  • The event goes directly to the Input Event Queue or is scheduled to be inserted to the Input Event Queue later. Scheduling is performed by the Event Scheduler.

  • From this queue the events are taken by the Filter Processing Unit. The list of filters defined for this event type is taken and then each of them is invoked. If all filters accept the event, it will be put to the Main Event Queue in which it will await to be processed by other event processors.

  • Then the event is collected by the Main Processing Unit. The list of event processors listening to this type of events is selected and then each of them is given the event to process.

  • After processing by the Main Processing Unit the event goes to the Output Event Queue if and only if it hasn’t been processed (i.e. listened to) by any of event processors.

3.7.3. Event cloning

The event is cloned each time when the periodically generated events are sent to the Input Event Queue.

The standard implementation of events allows choosing the cloning policy shallow or deep. These policies differ in the way of cloning of events attributes. When using the former, the references to attributes are copied - each event processor works on the same attribute instances. The policy deep executes the procedure of deep cloning, so each next generated event will contain individual copies of the attributes.

3.7.4. Custom events

The default implementation of an event is the AttributeMapEvent class. However, Sponge allows to use custom event implementations of the Event interface.

3.7.5. System events

System events are sent automatically by the engine. An event sent in your code shouldn’t have the same name as any of the system events. Currently there is only one system event.

Table 12. System events
Event name Description

startup

The startup event will be sent as the first event when the engine is starting up.

3.7.5.1. Startup system event

The startup system event could be useful to define rules or correlators that detect lack of other events since the startup of the engine.

The following rule detects a situation when there is no heartbeat event for 5 seconds since the startup of Sponge.

Example of startup system event
class DetectLackOfHearbeat(Rule):
    def onConfigure(self):
        self.events = ["startup", "heartbeat :none"]
        self.duration = Duration.ofSeconds(5)
    def onRun(self, event):
        print "No heartbeat!"

3.7.6. Control events

Control events are used by the engine internally. The names of control events have a prefix $. You shouldn’t give to your events a name that starts with this character.

3.7.7. Creating and sending events

Creating an event means creating an instance of an event class. Sending an event means that the created event will be put into the Input Event Queue, to be processed by filters and then by triggers, rules and correlators.

Event can be created and sent using the EventDefinition fluent API (e.g. sponge.event("helloEvent").set("say", "Hello World!").send()). The method sponge.event returns EventDefinition.

An event may be sent as:

  • A single instance – the event will be placed in the Input Event Queue only once.

  • Many instances periodically – new instances of an event will be placed in the Input Event Queue periodically, each of them with its own id and send time.

Table 13. EventDefinition methods
Method Description

EventDefinition set(String name, Object value)

Sets the event attribute.

EventDefinition modify(EventDefinitionModifier modifier)

Modifies the underlying event.

send()

Sends an event immediately.

sendAfter(delay)

Sends an event after a specified time (given in milliseconds or as a Duration). Note that the order of inserting events to the Input Event Queue may be different than the order of invocations of sendAfter, even with the same delay. It depends on the internal implementation of the Quartz library.

sendAfter(delay, interval)

Periodically sends events after a specified time (given in milliseconds or as a Duration) every interval (given in milliseconds or as a Duration).

sendAt(at)

Sends an event at the specified time (given in milliseconds as the number of milliseconds since 01/01/1970 or as an Instant).

sendAt(at, interval)

Periodically sends events starting at the specified time (given in milliseconds as the number of milliseconds since 01/01/1970 or as an Instant) every interval (given in milliseconds or as a Duration).

sendAt(crontabSpec)

Sends events at time specified by Cron compatible time entry.

Event make()

Only returns the newly created event without sending.

3.7.8. Examples of sending events

Sample sending of events from the level of a knowledge base:

sponge.event("e1").sendAfter(Duration.ofSeconds(1))

Sends the event named "e1" after 1 second from now.

sponge.event("e2").sendAfter(2000, 1000)

Sends the event named "e2" after 2 seconds from now. New events will be periodically generated and sent every second.

sponge.event("e2").set("color", "red").set("severity", 5).send()

Sends an event with attributes "color" and "severity" immediately.

sponge.event("alarm").sendAt("0-59 * * * * ?")

Sends an event at the time specified by Cron notation.

3.7.9. Event priorities

A priority may be assigned only to control events, that are used internally by the engine. For standard events the priority always equals to 0 and cannot be modified.

A priority defines a level of the importance of an event. Events are added to and taken from queues with respect to their priorities. Priority is a positive or negative integer and the higher the number is, the higher is the priority of an event and the event will be processed before the others.

3.8. Processors

Processors are the basic objects that you define in Sponge to implement your knowledge base behavior.

Types of processors:

  • Actions - processors that provide functionality similar to functions. They don’t listen to events.

  • Event processors - processors that perform specified operations using events they listen to.

    • Filters - event processors used for allowing only certain events to be later processed by other event processors.

    • Triggers - event processors that execute a specified code when an event happens.

    • Event set processors - event processors that process sets of events.

      • Rules - event set processors that detect sequences of events.

      • Correlators - event set processors that detect any set of events and could be also used for implementing any complex event processing that isn’t provided by filters, triggers or rules.

3.8.1. Creating processors

In order to define your processor in a script knowledge base, you have to create a class extending the base class pointed by a specific alias (e.g. Filter for filters). In order to define your processor in a Java knowledge base, you have to create a class extending a specific class (e.g. JavaFilter for filters).

A name of a processor is a name of a class defining this processor.

3.8.2. Enabling processors

The operation of registering a processor in the engine is called enabling. Registered processors are available to the engine to perform specific tasks. For example, after enabling an event processor starts listening to events it is interested in.

Processors could be enabled:

  • by auto-enable (this is the default setting for script-based processors),

  • manually.

3.8.2.1. Auto-enable

Sponge automatically enables all processors (i.e. actions, filters, triggers, rules and correlators) defined in a script knowledge base. This is done just before invoking the onLoad callback function in the knowledge base. Processor classes whose names start with the Abstract prefix are considered abstract and will not be automatically enabled.

As previously mentioned, the auto-enable feature scans only for processors defined in the scripting knowledge base. Enabling Java-based processors has to be done manually.

For non script knowledge bases (Java or Kotlin based) the auto-enable feature will scan only for processor classes nested in a corresponding knowledge base class. Other processors have to be enabled manually.

Example of processor inheritance and auto-enable
# This abstract action will not be automatically enabled.
class AbstractCalculateAction(Action):
    def calculateResult(self):
        return 1

# This action will be automatically enabled.
class CalculateAction(AbstractCalculateAction):
    def onCall(self):
        return self.calculateResult() * 2

You may turn off auto-enable by setting the autoEnable engine configuration parameter to false (for example in the Sponge XML configuration file). In that case you have to enable processors manually.

3.8.2.2. Manual enabling

In most cases enabling processors manually should be done in the onLoad callback function.

To manually enable any script-based processors in a script knowledge base you may use: sponge.enable() to enable one processor and sponge.enableAll() to enable many processors.

Example of enabling a script-based processor
def onLoad:
    sponge.enable(TriggerA)
Example of enabling script-based processors
def onLoad:
    sponge.enableAll(Trigger1, Trigger3)

To manually enable any Java-based processors in a script knowledge base you may use sponge.enableJava(), sponge.enableJavaAll() or sponge.enableJavaByScan(). The default name of a Java-based processor is its full Java class name.

The enableJavaByScan method enables Java-based processors by scanning a given packages in search of all non abstract processor classes. The scanning is performed by the Reflections library. The method parameters are compatible with the Reflections(Object…​) constructor.

Example of enabling one Java-based processor
def onLoad():
    sponge.enableJava(SameSourceJavaRule)
Example of enabling many Java-based processors
def onLoad():
    sponge.enableJavaAll(SameSourceJavaRule, SameSourceJavaRule2, SameSourceJavaRule3)
Example of enabling Java-based processors by scanning Java packages
def onLoad():
    sponge.enableJavaByScan("org.openksavi.sponge.integration.tests.core.scanning")

3.8.3. Disabling processors

Processors could be disabled only manually. To disable any script-based processors in a script knowledge base you may use sponge.disable() to disable one processor and sponge.disableAll() to disable many processors.

Example of disabling a script-based processor
def onLoad:
    sponge.disable(EchoAction)

To disable any Java-based processors in a script knowledge base you may use sponge.disableJava(), sponge.disableJavaAll() or sponge.disableJavaByScan().

Example of disabling a Java-based processor
def onLoad():
    sponge.disableJava(SameSourceJavaRule)

3.8.4. Properties and methods

Table 14. Processor properties and methods
Property / Method Description

onConfigure()

A configuration callback method that will be invoked when a processor is being enabled. This method is mandatory.

onInit()

An initialization callback method that will be invoked after onConfigure(), each time a new working instance of the processor is created.

name

A name of a processor. This is a shortcut for getName()/setName(text) methods. Because of names of processors are created automatically, the setter shouldn’t be used in a client code.

displayName

A display name of a processor. This is a shortcut for getDisplayName()/setDisplayName(text) methods. A display name is not used internally but could be useful in a client code.

description

A description of a processor. This is a shortcut for getDescription()/setDescription(text) methods. A description is not used internally but could be useful in a client code.

features

Features of a processor. This is a shortcut for getFeatures()/setFeatures(Map<String, Object>) methods. The processor features property is simply a map of String to Object associated with a processor definition (not instance). It could be used to provide a custom behavior in a client code.

logger

The read-only property that provides a processor logger. This is a shortcut for getLogger() method. A processor logger name has the following format: sponge.kb.<language>.<knowledgeBaseName>.<processorName>, e.g. sponge.kb.python.kb1.EchoAction for a python-based processor, sponge.kb.python.kb1.org.openksavi.sponge.examples.PowerEchoAction for a Java-based processor enabled in a Python-based knowledge base.

adapter

The read-only property that provides a processor adapter. This is a shortcut for getAdapter() method. A processor adapter is an internal object, associated with the processor, that is used by the engine. There should be no need to use this property in the client code.

In many scripting languages properties can be accessed using dot notation rather than a direct method call. For example a display name may be set using self.displayName = "Display name" or self.setDisplayName("Display name").

3.9. Actions

Actions provide functionality similar to synchronous functions. They may be used in many knowledge bases that are written in different languages.

The alias for the base class for script-based actions is Action. The base class for Java-based actions is JAction.

3.9.1. Properties and methods

In addition to the inherited processor properties and methods, actions provide the following ones.

Table 15. Action properties and methods
Property / Method Description

Object onCall(dynamic arguments)

The dynamic callback method that should be defined in an action. It will be invoked when an action is called, e.g.: sponge.call(actionName, [argument list]). The behavior of this method is dynamic, i.e. custom actions define onCall methods with the arbitrary number of named arguments, for example def onCall(self, value, text). This is the reason that the Action interface doesn’t force any implementation of onCall. The result is an Object. The implementation of this method is mandatory.

void onProvideArgs(Set<String> names, Map<String, Object> current, Map<String, ArgValue> provided)

A callback method that returns the provided argument values along with argument value sets (i.e. possible values of an argument). The provided arguments are explained later in this document.

The onConfigure method in actions is not mandatory.

3.9.2. Example in a script language

The code presented below defines the action named EchoAction that simply returns all arguments.

Action example
class EchoAction(Action): (1)
    def onCall(self, text): (2)
        return text

def onStartup():
    result = sponge.call("EchoAction", ["test"]) (3)
    logger.debug("Action returned: {}", result)
1 The definition of the action EchoAction. The action is represented by the class of the same name.
2 The action onCall dynamic callback method that takes one argument text.
3 Calls the action named "EchoAction" passing one argument.
Console output
Action returned: test

3.9.3. Example in Java

The code presented below defines the Java-based action named JavaEchoAction.

Java action example
public class JavaEchoAction extends JAction { (1)

    @Override
    public Object onCall(String text) { (2)
        return text;
    }
}
1 The definition of the action JavaEchoAction. The action is represented by the Java class of the same name.
2 The action onCall callback method.
Java action manual registration in the Python knowledge base
sponge.enableJava(JavaEchoAction)

3.9.4. Arguments and result metadata

Actions may have metadata specified in the onConfigure method. Metadata may describe action arguments and a result. Metadata are not verified by the engine while performing an action call but could be interpreted by a client code or Sponge plugins. For example they could be useful in a generic GUI that calls Sponge actions.

Metadata for arguments and a result support types.

Table 16. Argument metadata properties
Property Description

name

A mandatory argument name.

type

A mandatory argument type.

displayName

An optional argument display name.

description

An optional argument description.

optional

A not mandatory flag specifying if this argument is optional. Defaults to false. Optional arguments can be specified only as last in the argument list. Optional arguments are not required to be passed when calling an action.

provided

A flag specifying if this argument is provided. Defaults to false.

depends

A list of attribute names that this attribute depends on. Available only if the attribute is provided.

readOnly

A flag specifying if this argument is read only. Defaults to false. Available only if the attribute is provided.

overwrite

A flag specifying if the provided value of this argument should overwrite the value set in a client code. Defaults to false. Available only if the attribute is provided. This flag should be handled by a client code.

Table 17. Result metadata properties
Property Description

type

A mandatory result type.

displayName

An optional result display name.

description

An optional result description.

Action metadata example
class UpperCase(Action):
    def onConfigure(self):
        self.displayName = "Convert to upper case"
        self.description = "Converts a string to upper case."
        self.features = {"visibility":True}
        self.argsMeta = [
            ArgMeta("text", StringType()).displayName("Text to upper case").description("The text that will be converted to upper case."),
            ArgMeta("optionalText", AnyType().nullable(True)).displayName("Text suffix").optional()
        ]
        self.resultMeta = ResultMeta(StringType()).displayName("Upper case text")
    def onCall(self, text, optionalText = None):
        return text.upper() + ( " " + optionalText.upper() if optionalText is not None else "")
Action metadata example with multiple arguments
class MultipleArgumentsAction(Action):
    def onConfigure(self):
        self.displayName = "Multiple arguments action"
        self.argsMeta = [
            ArgMeta("stringArg", StringType().maxLength(10).format("ipAddress")),
            ArgMeta("integerArg", IntegerType().minValue(1).maxValue(100).defaultValue(50)),
            ArgMeta("anyArg", AnyType().nullable(True)),
            ArgMeta("stringListArg", ListType(StringType())),
            ArgMeta("decimalListArg", ListType(ObjectType("java.math.BigDecimal"))),
            ArgMeta("stringArrayArg", ObjectType("java.lang.String[]")),
            ArgMeta("javaClassArg", ObjectType("org.openksavi.sponge.examples.CustomObject")),
            ArgMeta("javaClassListArg", ListType(ObjectType("org.openksavi.sponge.examples.CustomObject"))),
            ArgMeta("binaryArg", BinaryType().mimeType("image/png").features({"width":28, "height":28, "color":"white"})),
        ]
        self.resultMeta = ResultMeta(BooleanType()).displayName("Boolean result")
    def onCall(self, stringArg, integerArg, anyArg, stringListArg, decimalListArg, stringArrayArg, javaClassArg, javaClassListArg, binaryArg):
        return True

For more information see ArgMeta and ResultMeta.

3.9.5. Provided arguments

An action argument can be provided, i.e. its value and possible value set may be computed and returned to a client code every time before calling an action. A provided argument gives more flexibility than the defaultValue in the argument data type.

This feature makes easier creating a generic UI for an action call that reads and presents the actual state of the entities that are to be changed or only viewed by the action and its arguments.

Example of an action with provided arguments
def onInit():
    sponge.setVariable("actuator1", "A")
    sponge.setVariable("actuator2", False)
    sponge.setVariable("actuator3", 1)
    sponge.setVariable("actuator4", 1)
    sponge.setVariable("actuator5", "X")

class SetActuator(Action):
    def onConfigure(self):
        self.displayName = "Set actuator"
        self.description = "Sets the actuator state."
        self.argsMeta = [
            ArgMeta("actuator1", StringType()).displayName("Actuator 1 state").provided(),
            ArgMeta("actuator2", BooleanType()).displayName("Actuator 2 state").provided(),
            ArgMeta("actuator3", IntegerType().nullable()).displayName("Actuator 3 state").provided().readOnly(),
            ArgMeta("actuator4", IntegerType()).displayName("Actuator 4 state"),
            ArgMeta("actuator5", StringType()).displayName("Actuator 5 state").provided().depends("actuator1"),
        ]
        self.resultMeta = ResultMeta(VoidType())
    def onCall(self, actuator1, actuator2, actuator3, actuator4, actuator5):
        sponge.setVariable("actuator1", actuator1)
        sponge.setVariable("actuator2", actuator2)
        # actuator3 is read only in this action.
        sponge.setVariable("actuator4", actuator4)
        sponge.setVariable("actuator5", actuator5)
    def onProvideArgs(self, names, current, provided):
        if "actuator1" in names:
            provided["actuator1"] = ArgValue().value(sponge.getVariable("actuator1", None)).valueSet(["A", "B", "C"])
        if "actuator2" in names:
            provided["actuator2"] = ArgValue().value(sponge.getVariable("actuator2", None))
        if "actuator3" in names:
            provided["actuator3"] = ArgValue().value(sponge.getVariable("actuator3", None))
        if "actuator4" in names:
            provided["actuator4"] = ArgValue().valueSet([2, 4, 8])
        if "actuator5" in names:
            provided["actuator5"] = ArgValue().value(sponge.getVariable("actuator5", None)).valueSet(["X", "Y", "Z", current["actuator1"]])

def onStartup():
    sponge.logger.debug("The provided value of actuator1 is: {}", sponge.provideActionArgs("SetActuator", ["actuator1"])["actuator1"].getValue())
Console output
The provided value of actuator1 is: A

A provided argument can be readOnly. In that case its value in the onCall method should be ignored. A read only attribute type has to be nullable.

A provided argument can depend on other arguments but only those that are specified earlier. In the example argument actuator5 depends on actuator1. Its possible value set contains the value of actuator1.

Arguments configured as provided have to be calculated in the onProvideArgs callback method and set in the provided map. For each provided argument its value and possible value set can be produced as the instance of the ArgValue class. The optional value method sets the provided value and the optional valueSet method sets the possible value set.

The parameter names in the onProvideArgs is a set of argument names that are to be provided. The current parameter is a not null map of argument names and their current values passed from a client code. The current value means the value used in a client code, for example entered by a user into an UI before calling the action. This map is required to contain values of those arguments that the arguments specified in the names depend on.

3.9.6. Implementing interfaces

Actions may implement additional Java interfaces. It could be used to provide custom behavior of actions.

Action implementing a Java interface
from org.openksavi.sponge.integration.tests.core import TestActionVisibiliy

class EdvancedAction(Action, TestActionVisibiliy): (1)
    def onCall(self, text):
        return text.upper()
    def isVisible(self, context):
        return context == "day"
1 The Java interface TestActionVisibiliy declares only one method boolean isVisible(Object context).

3.10. Event processors

Event processors are processors that perform asynchronous operations using events they listen to.

Instances of event processors, depending on their type, may be created:

  • only once, while enabling, so they are treated as singletons,

  • many times.

Table 18. Event processors
Event processor Singleton

Filter

Yes

Trigger

Yes

Rule

No

Correlator

No

When configuring an event processor, each event name can be specified as a regular expression thus creating a pattern matching more event names. The regular expression has to be compatible with java.util.regex.Pattern.

Event name pattern example
class TriggerA(Trigger):
    def onConfigure(self):
        self.event = "a.*" (1)
    def onRun(self, event):
        self.logger.debug("Received event: {}", event.name)
1 The trigger will listen to all events whose name starts with "a", as specified by the regular expression.

Event processors shouldn’t implement infinite loops in their callback methods because it would at least disrupt the shutdown procedure. If you must create such a loop, please use for example while sponge.engine.isRunning(): rather than while True:.

3.10.1. Filters

Filters allow only certain events to be processed by the engine. Filters are executed in the same order as the order of their registration (i.e. enabling).

You could modify event attributes in filters if necessary.

The alias for the base class for script-based filters is Filter. The base class for Java-based filters is JFilter.

3.10.1.1. Properties and methods

In addition to the inherited processor properties and methods, filters provide the following ones.

Table 19. Filter properties and methods
Property / Method Description

event or events

These properties should be used to set the names (or name patterns) of filtered events. They may be used only as setters. They are shortcuts for setEvent(eventName) and setEvents(eventNames) methods respectively. You may use only one of them. The property event allows setting one event name. The property events is an array of event names. This property is mandatory. It should be set in the onConfigure callback method.

boolean onAccept(event)

This method checks if an incoming event should be further processed. If onAccept method returns false, then the event will be discarded. Otherwise it will be processed by the other event processors. This method is mandatory.

Every filter should implement abstract onConfigure and onAccept methods.

3.10.1.2. Example in a script language

The code presented below creates a filter which filters only events whose name is "e1". Other events are not processed by this filter. Events e1 successfully pass through the filter only if they have an attribute "color" set to the value "blue". The others are rejected.

Class methods defined in a Python class have an instance object (self) as the first parameter.
Filter example
class ColorFilter(Filter): (1)
    def onConfigure(self): (2)
        self.event = "e1" (3)
    def onAccept(self, event): (4)
        self.logger.debug("Received event {}", event) (5)
        color = event.get("color", None) (6)
        if (color is None or color != "blue"): (7)
            self.logger.debug("rejected")
            return False
        else: (8)
            self.logger.debug("accepted")
            return True
1 The definition of the filter ColorFilter. The filter is represented by the class of the same name.
2 The filter configuration callback method.
3 Sets up ColorFilter to listen to e1 events (i.e. events named "e1").
4 The filter onAccept method will be called when an event e1 happens. The event argument specifies that event instance.
5 Logs the event.
6 Assigns the value of the event attribute "color" to the local variable color.
7 If color is not set or is not "blue" then rejects that event by returning false.
8 Otherwise accepts the event by returning true.

The filter ColorFilter will be enabled automatically. The enabling creates one instance of ColorFilter class and invokes ColorFilter.onConfigure method to set it up. Since that moment the filter listens to the specified events.

3.10.1.3. Example in Java

The filter presented below checks if an event named "e1" or "e2" or "e3" has an attribute "shape" set. If not, an event is ignored and will not be processed further.

Java filter example
public class ShapeFilter extends JFilter { (1)

    @Override
    public void onConfigure() { (2)
        setEvents("e1", "e2", "e3"); (3)
    }

    @Override
    public boolean onAccept(Event event) { (4)
        String shape = event.get("shape", String.class); (5)
        if (shape == null) {
            getLogger().debug("No shape for event: {}; event rejected", event);
            return false; (6)
        }

        getLogger().debug("Shape is set in event {}; event accepted", event);

        return true; (7)
    }
}
1 The definition of the filter ShapeFilter. The filter is represented by the Java class of the same name.
2 The filter configuration callback method.
3 Sets up ShapeFilter to listen to e1, e2 and e3 events.
4 The filter onAccept method will be called when any of these events happen. The event argument specifies that event instance.
5 Assigns a value of an event attribute "shape" to the local variable shape.
6 If shape is not set then rejects that event by returning false.
7 Otherwise accepts the event by returning true.

This Java-based filter can be enabled only manually, for example in a script knowledge base e.g.:

Enabling a Java-based filter
sponge.enableJava(ShapeFilter)

3.10.2. Triggers

Triggers run a specified code when an event happens.

The alias for the base class for script-based triggers is Trigger. The base class for Java-based filters is JTrigger.

3.10.2.1. Properties and methods

In addition to the inherited processor properties and methods, triggers provide the following ones.

Table 20. Trigger properties and methods
Property / Method Description

event or events

These properties should be used to set the names (or name patterns) of the events that cause this trigger to fire. They may be used only as setters. They are shortcuts for setEvent(eventName) and setEvents(eventNames) methods respectively. You may use only one of them. The property event allows setting one event name. The property events is an array of event names. This property is mandatory. It should be set in the onConfigure callback method.

onRun(event)

The callback method used for processing the event, called when the specified event (or one of the events) happens. This method is mandatory.

boolean onAccept(event)

This optional callback method checks if an incoming event should processed by this trigger. The default implementation returns true.

Every trigger should implement abstract onConfigure and onRun methods.

3.10.2.2. Example in a script language

The code presented below defines the trigger named TriggerA listening to events named "a".

Trigger example
class TriggerA(Trigger): (1)
    def onConfigure(self): (2)
        self.event = "a" (3)
    def onRun(self, event): (4)
        self.logger.debug("Received event: {}", event.name) (5)
1 The definition of the trigger TriggerA. The trigger is represented by a class of the same name.
2 The trigger configuration callback method.
3 Sets up TriggerA to listen to a events (i.e. events that have name "a").
4 The trigger onRun method will be called when an event a happens. The event argument specifies that event instance.
5 Logs the event.

The trigger TriggerA will be enabled automatically. The enabling creates an instance of TriggerA class and invokes TriggerA.onConfigure method to set it up. Since that moment the trigger listens to the specified events.

3.10.2.3. Example in Java

The code presented below defines the trigger named SampleJavaTrigger listening to events named "e1".

Java trigger example
public class SampleJavaTrigger extends JTrigger { (1)

    @Override
    public void onConfigure() { (2)
        setEvent("e1"); (3)
    }

    @Override
    public void onRun(Event event) { (4)
        getLogger().debug("Received event {}", event); (5)
    }
}
1 The definition of the trigger SampleJavaTrigger. The trigger is represented by a Java class of the same name.
2 The trigger configuration callback method.
3 Sets up SampleJavaTrigger to listen to e1 events (i.e. events that have name "e1").
4 The trigger onRun method will be called when an event e1 happen. The event argument specifies that event instance.
5 Logs the event.
Java trigger manual registration in the script knowledge base
sponge.enableJava(SampleJavaTrigger)

3.10.3. Rules

Sometimes there is a need to perform certain actions when a sequence of events has happened, additionally fulfilling some conditions. To handle such event relationships (both temporal and logical), Sponge provides rules. It is important for the behavior of the rules that events that happened first must be sent first into the engine.

The alias for the base class for script-based rules is Rule. The base class for Java-based rules is JRule.

A rule group is a set of rule instances, each created automatically for every event that could be accepted as the first event of this rule.

3.10.3.1. Properties and methods

In addition to the inherited processor properties and methods, rules provide the following ones.

Table 21. Rule properties and methods
Property / Method Description

onConfigure()

The callback method that is invoked only once, when a rule is being enabled. In this method it should be established for what type of events the rule listens. Optionally event conditions for incoming events or rule duration could be set. This method is mandatory.

onInit()

The initialization callback method that is invoked while creating every new rule instance but after onConfigure.

events

This property should be used to set the specifications of events whose sequence causes the rule to fire. It may be used only as a setter. It is a shortcut for the setEvents(eventStringSpecs) method.

ordered

The boolean property indicating that the rule should listen to ordered (ordered rule) or unordered (unordered rule) sequences of events. The default is true, i.e. the rule would listen to ordered sequences of events. It is a shortcut for the setOrdered(boolean) method.

addConditions(alias, conditions)

Adds conditions for an event specified by an alias (or event name if aliases are not used). A condition is a method of this class or a closure/lambda that is invoked to verify that a new incoming event corresponds to this rule. The name of the condition method is irrelevant.

addAllConditions(conditions)

Adds conditions for all events. This method must be invoked after the event specifications.

duration

This property may be used to set the time how long a rule lasts (represented as a Duration). It is a shortcut for the setDuration(value) method. The instance of a rule will be active only for a given period of time since the arrival of the first event. Until that time the instance of the rule will fire for each suitable event sequence that happens.

onRun(event)

The callback method invoked when a sequence of events specified by this rule has happened and all the conditions have been fulfilled. The argument event is the reference to the final event that caused this rule to fire. There could be many sequences of events fitting the rule definition. In order to access the events which fulfilled the conditions and caused the rule fire, the getEvent(eventAlias) method should be used. The onRun method is mandatory.

Event getEvent(String eventAlias)

Returns the instance of the event that already happened and that has a specified alias. This method may be used inside onRun method. If an event hasn’t happened yet, this method throws an exception. This method may return null only when an event that supposed not to happen didn’t occur as specified.

firstEvent

This property is a reference to the first event that has been accepted by this rule. It is a shortcut for the Event getFirstEvent() method. It could be used for example in event condition methods (including the one for the first event itself).

eventSequence

Returns a sequence of events that happened, as a list of event instances. The sequence may contain null values when an event that supposed not to happen didn’t occur as specified. This method may be used inside onRun method.

synchronous

This property may be used to set a boolean synchronous flag for a rule. It is a shortcut for the setSynchronous(synchronous) method. If a rule is synchronous it means that an event will be processed sequentially (in one thread) for all instances of this rule. If a rule is asynchronous then an event will be processed by the instances of this rule concurrently (in many threads). If the synchronous flag is not set then the default value as specified by eventSetProcessorDefaultSynchronous configuration parameter will be used. In most cases there should be no need to change this flag.

Every rule should implement the abstract onConfigure and onRun methods.

Because of rules are not singletons the onConfigure() method is invoked only once, while enabling the rule. So it should contain only basic configuration as stated before. The onInit() method must not contain such configuration because it is invoked every time the new instance of the rule is created.
A duration is relative to an internal clock of the engine, that is related to the time of events. When a duration timeout occurs, the engine sends a control event (DurationControlEvent) to the Input Event Queue so that the control event, before finishing the rule, goes the same route as all events. This is to ensure that no events will be skipped by a rule if the system is highly loaded. Note that this may cause the rule to last longer in terms of an external clock.
3.10.3.2. Event specification

Event specification for the rule consists of:

Event name

A name (or name pattern) of the event (mandatory).

Event alias

An optional alias for the event. The alias is a unique (in the scope of the rule) name assigned to the event. Aliases are mandatory if there is more than one event of the same type (i.e. having the same name). When each of the events is of different type, there is no need to specify an alias. In such case aliases will be defined automatically and equal to the name of the corresponding event.

Event mode

Specifies which sequences of events suitable to this rule should be used for running the rule (i.e. invoking the onRun callback method). Event modes are defined in the EventMode Java enumeration.

Table 22. Rule event modes
Event mode Description

first

The first suitable event. This is the default event mode when none is specified for an event.

last

The last suitable event for the duration of the rule.

all

All suitable events for the duration of the rule.

none

An event that cannot happen in the sequence.

Event specification should be formatted as text "eventName [eventAlias [:eventMode"]] or "eventNamePattern [eventAlias [:eventMode"]]. White characters between all elements are allowed. For example the specifications "event1 e1 :first", "event1", "event1 e1" define the suitable first event named "event1". The specification "[Ee]vent.* e" define all events which name starts with "Event" or "event".

3.10.3.3. Ordered rules

For ordered rules:

  • The first event in the sequence, i.e. the event that would initiate the rule, must always have the mode first.

  • If the mode of the last (final) specified event is last or none, a duration must be set. Otherwise the rule would never fire.

The following examples of complete event specifications assume that the ordered rule has a duration that spans over all incoming events listed in the second column. The integer value in the brackets is the id of the event. An element null means that the event hasn’t happened. Incoming events: e1[1], e2[2], e2[3], e3[4], e2[5], e3[6], e3[7].

Table 23. Examples of ordered event specifications
Events specification Event sequences

["e1", "e2 :all", "e3 :all"]

[e1[1], e2[2], e3[4]], [e1[1], e2[3], e3[4]], [e1[1], e2[2], e3[6]], [e1[1], e2[3], e3[6]], [e1[1], e2[5], e3[6]], [e1[1], e2[2], e3[7]], [e1[1], e2[3], e3[7]], [e1[1], e2[5], e3[7]]

["e1", "e2 :all", "e3"]

[e1[1], e2[2], e3[4]], [e1[1], e2[3], e3[4]]

["e1", "e2 :all", "e3 :last"]

[e1[1], e2[2], e3[7]], [e1[1], e2[3], e3[7]], [e1[1], e2[5], e3[7]]

["e1", "e2 :all", "e4 :none"]

[e1[1], e2[2], null], [e1[1], e2[3], null], [e1[1], e2[5], null]

["e1", "e2", "e3 :all"]

[e1[1], e2[2], e3[4], [e1[1], e2[2], e3[6]], [e1[1], e2[2], e3[7]]

["e1", "e2", "e3"]

[e1[1], e2[2], e3[4]]

["e1", "e2", "e3 :last"]

[e1[1], e2[2], e3[7]]

["e1", "e2", "e4 :none"]

[e1[1], e2[2], null]

["e1", "e2 :last", "e3 :all"]

[e1[1], e2[3], e3[4]], [e1[1], e2[5], e3[6]], [e1[1], e2[5], e3[7]]

["e1", "e2 :last", "e3"]

[e1[1], e2[3], e3[4]]

["e1", "e2 :last", "e3 :last"]

[e1[1], e2[5], e3[7]]

["e1", "e2 :last", "e4 :none"]

[e1[1], e2[5], null]

["e1", "e4 :none", "e3 :all"]

[e1[1], null, e3[4]], [e1[1], null, e3[6]], [e1[1], null, e3[7]]

["e1", "e4 :none", "e3"]

[e1[1], null, e3[4]]

["e1", "e4 :none", "e3 :last"]

[e1[1], null, e3[7]]

["e1", "e2", "e3 :none"]

This rule hasn’t been fired because the event e3 wasn’t supposed to happen.

3.10.3.4. Unordered rules

For unordered rules:

  • The matching of unordered events is done starting from the left in the list of events the unordered rule listens to.

  • Every event that is relevant to the unordered rule causes a new instance of the rule to be created. This implicates that the event mode for an event that actually happens as the first is used by the engine only as a suggestion. So the actual order of events that happen has a significant impact on the behavior of unordered rules.

  • If at least one specified event has none mode, you probably should set a duration for such a rule to avoid superfluous instances of the rule.

Unordered rules is a new feature that should be treated as an experimental one.
3.10.3.5. Event conditions

A rule may define conditions for events that have to be met to consider an incoming event as corresponding to the rule:

  • of the form of a any class method that takes one argument (Event) and returns boolean, e.g.:

    boolean conditionA(Event event);
    boolean check1(Event event);
  • as a closure or a lambda (depending on the language) that takes two arguments (Rule, Event) and returns boolean, e.g.:

    lambda rule, event: Duration.between(rule.getEvent("filesystemFailure").time, event.time).seconds > 2
  • as an instance of an implementation of the interface EventCondition (takes two arguments (Rule, Event) and returns boolean), e.g. as a Java lambda expression:

    (rule, event) -> {
        return true;
    };

An event condition in Java is represented by the interface EventCondition.

A condition in the form of a closure or a lambda specifies two arguments: a rule instance (determined at the runtime) and an event instance. Take care not to mix up the rule argument with this (in Java) or self (in Python) as they are references to different objects.

The condition methods tell if an incoming event (corresponding to the sequence of events specified by the rule) should be considered suitable.

3.10.3.6. Example in a script language

The code presented below defines a rule named SameSourceAllRule listening to an ordered sequence of events ("filesystemFailure", "diskFailure"). The two events have to have severity greater than 5 and the same source. Moreover the second event has to happen not later than after 4 seconds since the first one. The method onRun() will be invoked for every sequence of events that match this definition.

Rule example
class SameSourceAllRule(Rule): (1)
    def onConfigure(self): (2)
        # Events specified with aliases (e1 and e2)
        self.events = ["filesystemFailure e1", "diskFailure e2 :all"] (3)
        self.addAllConditions(self.severityCondition) (4)
        self.addConditions("e2", self.diskFailureSourceCondition) (5)
        self.duration = Duration.ofSeconds(8) (6)
    def onRun(self, event): (7)
        self.logger.info("Monitoring log [{}]: Critical failure in {}! Events: {}",
            event.time, event.get("source"), self.eventSequence) (8)
    def severityCondition(self, event): (9)
        return int(event.get("severity")) > 5 (10)
    def diskFailureSourceCondition(self, event): (11)
        event1 = self.getEvent("e1") (12)
        return event.get("source") == event1.get("source") and \
            Duration.between(event1.time, event.time).seconds <= 4 (13)
1 The definition of the rule SameSourceAllRule. The rule is represented by a class of the same name.
2 The rule configuration callback method.
3 Defines that the rule is supposed to wait for sequences of events "filesystemFailure" (alias "e1") and "diskFailure" (alias "e2") and take into consideration the first occurrence of "e1" event and all occurrences of "e2" event.
4 Sets the condition checking an event severity for all events.
5 Sets conditions checking "e2" event source.
6 Setting the duration of the rule. The duration must be set for this rule because the final event has all mode. The rule lasts for 8 seconds. So, for 8 seconds since the occurrence of the first matching e1 a tree of event instances will be constantly built with the root containing the instance of initial e1 event. Each matching e2 event will cause the rule to fire immediately for the current event sequence. After reaching the duration time this rule instance will be discarded.
7 The onRun method will be called when the proper sequence of events happens and all the conditions have been fulfilled. The event argument specifies the last event in that sequence.
8 Logs message and the sequence of events.
9 An event condition method severityCondition.
10 Accept only events that have severity greater than 5.
11 An event condition method diskFailureSourceCondition.
12 Assigns the first event (e1) to the local variable event1.
13 Accept e2 events that have the same source as the first event e1 and that happened not later than after 4 seconds since the corresponding e1 event.

The rule will be enabled automatically. Then, in case of occurrence of e1 event that has severity greater than 5, a new instance of a rule SameSourceAllRule will be created.

A condition could be expressed as a lambda function, for example:

self.addConditions("e1", lambda rule, event: int(event.get("severity")) > 5)
3.10.3.7. Example in Java

The code presented below defines a rule analogous to the one shown above but defined as a Java class.

Java rule example
public class SameSourceJavaRule extends JRule { (1)

    private static final Logger logger = LoggerFactory.getLogger(SameSourceJavaRule.class);

    @Override
    public void onConfigure() {
        setEvents(new Object[] { makeEventSpec("filesystemFailure", "e1"), makeEventSpec("diskFailure", "e2", EventMode.ALL) }); (2)

        addAllConditions("severityCondition"); (3)
        addConditions("e2", (rule, event) -> { (4)
            Event event1 = rule.getEvent("e1");
            return event.get("source").equals(event1.get("source")) &&
                    Duration.between(event1.getTime(), event.getTime()).getSeconds() <= 4;
        });

        setDuration(Duration.ofSeconds(8)));
    }

    @Override
    public void onRun(Event event) {
        logger.info("Monitoring log [{}]: Critical failure in {}! Events: {}", event.getTime(), event.get("source"),
                getEventAliasMap());
    }

    public boolean severityCondition(Event event) { (5)
        return event.get("severity", Number.class).intValue() > 5;
    }
}
1 The definition of the rule SameSourceAllRule. The rule is represented by a Java class of the same name.
2 The makeEventSpec method is used here to create event specifications instead of a formatted String. The same setting could be achieved by setEvents("filesystemFailure e1", "diskFailure e2 :all").
3 Sets the condition checking an event severity for all events.
4 Sets conditions checking "e2" event source (as a Java lambda expression).
5 An event condition method severityCondition.
Java rule manual registration in the Python knowledge base
sponge.enableJava(SameSourceJavaRule)

3.10.4. Correlators

Correlators could be viewed as a generalized form of rules. They detect correlations between events and could be used for implementing any complex event processing that isn’t provided by filters, triggers or rules.

Correlators listen to the specified events regardless of their order and provide manual processing of each such event. It means that they require more programming than the other processors, however provide more customized behavior. For example they need explicit stopping by calling the finish method. An instance of a correlator is created when the correlator accepts an incoming event as its first event.

A correlator instance, when started, may be finished:

  • manually by invoking the finish method from inside the onEvent method,

  • automatically when duration is set and the duration timeout takes place.

The alias for the base class for script-based correlators is Correlator. The base class for Java-based correlators is JCorrelator.

A correlator group is a set of instances of the correlator.

3.10.4.1. Properties and methods

In addition to the inherited processor properties and methods, correlators provide the following ones.

Table 24. Correlator properties and methods
Property / Method Description

onConfigure()

The configuration callback method that is invoked when the correlator is being enabled. In this method it should be established for what type of events this correlator listens. Optionally a correlator duration could be set. This method is mandatory.

events

This property should be used to set the names (or name patterns) of events that this correlator listens to. It may be used only as a setter. It is a shortcut for setEvents(eventNames) method. This mandatory property must be set in the onConfigure callback method.

maxInstances

This property may be used to set the maximum number of concurrent instances allowed for this correlator. It is a shortcut for setMaxInstances(value) method. If this value is not set, there will be no limit of concurrent instances. In that case you will probably need to implement onAcceptAsFirst() method.

instanceSynchronous

The instance synchronous flag. If true (the default value), one instance of the correlator will process only one event at a time. If false, one instance of the correlator will process many events concurrently. In that case the correlator has to be thread safe.

duration

This property may be used to set the time how long a correlator lasts (represented as a Duration). It is a shortcut for setDuration(value) method. The instance of a correlator will be active only for a given period of time since the arrival of the first accepted as first event. After that time on the instance of this correlator the onDuration callback method will be invoked.

boolean onAcceptAsFirst(Event event)

Checks if the event should be accepted as the first event of a correlator, therefore starting a new working instance. The method onAcceptAsFirst is invoked after onConfigure. This method is optional. The default implementation returns true.

onInit()

The initialization callback method that is invoked while creating a new correlator instance but after onAcceptAsFirst if it returns true. This method is optional.

onEvent(Event event)

The callback method invoked when an event that a correlator listens to happens. This method is mandatory.

firstEvent

This property is a reference to the first event that has been accepted by this correlator. It is a shortcut for the Event getFirstEvent() method. It could be used for example in the onEvent callback method.

onDuration()

The callback method invoked when the duration timeout occurs. This method should be implemented if a duration timeout is set. After invoking this callback method, finish is invoked automatically.

finish()

The final method that should be invoked in onEvent(Event event) method when the correlator has done its work. Only by invoking finish this instance of the correlator is closed and its resources are released.

synchronous

This property may be used to set a boolean synchronous flag for a correlator. For details see a description of this flag for rules.

Every correlator may implement the onAcceptAsFirst method and should implement the abstract onEvent method. If a duration is set up, the onDuration callback method should be implemented as well.

Because of correlators are not singletons the onConfigure method is invoked only once while enabling the correlator. So it should contain only basic configuration as stated before. The onInit method must not contain such configuration because it is invoked later, every time a new instance of the correlator is created.
3.10.4.2. Example in a script language

The code presented below defines the correlator named SampleCorrelator that listens to events "filesystemFailure" and "diskFailure". The maximum number of concurrent instances allowed for this correlator is set to 1. A filesystemFailure event will be accepted as the first event only when there is no instance of this correlator already running. When the filesystemFailure event is accepted as the first, a new instance of this correlator will be created. Each instance of this correlator adds to its internal event log list eventLog any suitable event. When 4 fitting events are collected the correlator instance will finish.

Correlator example
class SampleCorrelator(Correlator): (1)
    def onConfigure(self): (2)
        self.events = ["filesystemFailure", "diskFailure"] (3)
        self.maxInstances = 1 (4)
    def onAcceptAsFirst(self, event): (5)
        return event.name == "filesystemFailure" (6)
    def onInit(self): (7)
        self.eventLog = [] (8)
    def onEvent(self, event): (9)
        self.eventLog.append(event) (10)
        self.logger.debug("{} - event: {}, log: {}", self.hashCode(), event.name, str(self.eventLog))
        if len(self.eventLog) == 4:
            self.finish() (11)
1 The definition of the correlator SampleCorrelator. The correlator is represented by a class of the same name.
2 The correlator configuration callback method.
3 Define that the correlator is supposed to listen to events "filesystemFailure" and "diskFailure" (in no particular order).
4 Sets the maximum number of concurrent instances.
5 The correlator onAcceptAsFirst callback method.
6 The correlator will accept as the first an event named filesystemFailure.
7 The correlator initialization callback method. It is invoked after onAcceptAsFirst.
8 Setting an initial value to the field eventLog.
9 The correlator onEvent callback method.
10 Adds a new event to eventLog.
11 This correlator instance will finish when 4 fitting events are collected into eventLog.

The correlator will be enabled automatically. Then, in case of acceptance of an event, a new instance of a correlator SampleCorrelator will be created.

3.10.4.3. Example in Java

The code presented below defines the correlator analogous to the one shown above but defined as a Java class.

Java correlator example
public class SampleJavaCorrelator extends JCorrelator { (1)

    private List<Event> eventLog;

    public void onConfigure() {
        setEvents("filesystemFailure", "diskFailure");
        setMaxInstances(1);
    }

    public boolean onAcceptAsFirst(Event event) {
        return event.getName().equals("filesystemFailure");
    }

    public void onInit() {
        eventLog = new ArrayList<>();
    }

    public void onEvent(Event event) {
        eventLog.add(event);
        getLogger().debug("{} - event: {}, log: {}", hashCode(), event.getName(), eventLog);
        if (eventLog.size() >= 4) {
            finish();
        }
    }
}
1 The definition of the correlator SampleJavaCorrelator. The correlator is represented by a Java class of the same name.
Java correlator manual registration in the Python knowledge base
sponge.enableJava(SampleJavaCorrelator)

3.11. Plugins

Plugins are used for expanding Sponge with new functionalities and use them in knowledge bases. Typically they provide access to and from external systems.

The alias for the base class for script-based plugins is Plugin. The base class for Java-based plugins is JPlugin.

Each of these base classes extends the BasePlugin class that provides empty implementations of callback methods. If the created plugin requires own configuration parameters (e.g. in the XML configuration file) the onConfigure method should be implemented.

Each plugin is also an engine module and that means that in inherits from the BaseEngineModule class.

Plugins could be written in Java or in a supported scripting language as a part of a scripting knowledge base. However plugins written in a scripting language must be used only in the same scripting knowledge base they were defined in. That is because there are limitations of scripting languages interoperation. Only plugins written in Java could be used in any scripting knowledge base.

3.11.1. Properties and methods

Table 25. Plugin properties and methods
Property / Method Description

name

The property that is a name of a plugin. This is a shortcut for getName()/setName(text) methods. Because of names of plugins are created automatically, the setter shouldn’t be used in a client code.

onConfigure(Configuration configuration)

The configuration callback method that will be invoked after a plugin has been loaded. This method allows reading an XML configuration for the plugin.

onInit()

The initialization callback method that will be invoked after a configuration of a plugin.

onStartup()

The callback method that will be invoked once after the startup of the engine.

onShutdown()

The callback method that will be invoked once before the shutdown of the engine.

onBeforeReload()

The callback method that will be invoked before every reloading of a knowledge base.

onAfterReload()

The callback method that will be invoked after every reloading of a knowledge base.

logger

The read-only property that provides a plugin logger. This is a shortcut for getLogger() method. A plugin logger name has the following format: sponge.kb.plugin.<pluginName>. Example logger name: sponge.kb.plugin.scriptPlugin.

3.11.2. Example in Java

Definition of the plugin in the XML configuration file
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">
    <knowledgeBases>
        <knowledgeBase name="sampleKnowledgeBase">
            <file>plugins_java.py</file>
        </knowledgeBase>
    </knowledgeBases>
    <plugins>
        <plugin name="echoPlugin" class="org.openksavi.sponge.examples.EchoPlugin">
            <configuration>
                <echo>Echo test!</echo>
                <count>2</count>
            </configuration>
        </plugin>
    </plugins>
</sponge>

This plugin definition section contains:

  • The unique name of the plugin. This name may be used in knowledge bases as a variable referencing this plugin instance.

  • The class name of the plugin. It could be a Java class name or a scripting language class name. If the plugin is defined in a scripting knowledge base than you must specify that knowledge base name as an XML tag <knowledgeBaseName>.

  • Custom configuration for a plugin. That section could be any XML that is understood by this plugin.

The above configuration defines a plugin implemented by org.openksavi.sponge.examples.EchoPlugin class. This plugin may be used in the knowledge base as a global variable named echoPlugin (according to the name attribute). There are additional configuration parameters defined for this plugin. These parameters could be read in the onConfigure() method of the plugin class, called before starting the plugin.

Java plugin example
public class EchoPlugin extends JPlugin { (1)

    private static final Logger logger = LoggerFactory.getLogger(EchoPlugin.class);

    private String echo = "noecho";

    private int count = 1;

    public EchoPlugin() {
    }

    @Override
    public void onConfigure(IConfiguration configuration) {  (2)
        echo = configuration.getString("echo", echo);
        count = configuration.getInteger("count", count);
    }

    @Override
    public void onInit() {  (3)
        logger.debug("Initializing {}", getName());
    }

    @Override
    public void onStartup() { (4)
        logger.debug("Starting up {}", getName());
    }

    public String getEcho() {
        return echo;
    }

    public void setEcho(String echo) {
        this.echo = echo;
    }

    public int getCount() {
        return count;
    }

    public String getEchoConfig() {
        return echo + " x " + count;
    }

    public void sendEchoEvent() {
        getSponge().event("echoEvent").set("echo", getEcho()).send();
    }
}
1 The definition of the plugin class.
2 The plugin configuration callback method.
3 The plugin initialization callback method.
4 The plugin startup callback method.

3.11.3. Using plugins

Using plugin in a script knowledge base
class PluginTrigger(Trigger):
    def onConfigure(self):
        self.event = "e1"
    def onRun(self, event):
        self.logger.debug("Echo from the plugin: {}", echoPlugin.echo) (1)
1 Obtaining echo bean property from the plugin that is an instance of the class EchoPlugin.

An access to the plugin could be achieved in two ways:

  • directly using the name echoPlugin as any other scripting language variable (this is the preferred way),

  • by using the sponge API, e.g. plugin = sponge.getPlugin("echoPlugin").

Because echoPlugin implements the method getEcho(), you may invoke it in two ways:

  • sponge.getPlugin("echoPlugin").echo

  • echoPlugin.echo

3.11.4. Example in a script language

Definition of the plugin in the XML configuration file
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">
    <knowledgeBases>
        <knowledgeBase name="sampleKnowledgeBase">
            <file>plugins_kb.py</file>
        </knowledgeBase>
    </knowledgeBases>
    <plugins>
        <plugin name="scriptPlugin" class="ScriptPlugin" knowledgeBaseName="sampleKnowledgeBase">
            <configuration>
                <storedValue>Value A</storedValue>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Scripting language plugin example
class ScriptPlugin(Plugin):
    def onConfigure(self, configuration):
        self.storedValue = configuration.getString("storedValue", "default")
    def onInit(self):
        self.logger.debug("Initializing {}", self.name)
    def onStartup(self):
        self.logger.debug("Starting up {}", self.name)
    def getStoredValue(self):
        return self.storedValue
    def setStoredValue(self, value):
        self.storedValue = value

3.11.5. Plugin life cycle

Sponge loads plugins when starting the system according to the steps:

  1. Creates the plugin class instance. The class must have a no-parameter constructor.

  2. Configures the plugin by invoking the method onConfigure().

  3. Initializes the plugin by invoking the method onInit().

  4. Invokes the callback method onStartup() when starting the engine.

  5. After starting all plugins the methods onStartup() defined in all knowledge bases are invoked.

  6. In case of reloading a knowledge base, the method onBeforeReload() of each plugin is invoked before the method onBeforeReload() of knowledge bases. Invoking the methods onAfterReload() goes in reverse (first the methods onAfterReload() of all knowledge bases and then the methods defined in plugins).

  7. Before Sponge shuts down, methods onShutdown() of all knowledge bases are invoked and then the method onShutdown() is invoked for each plugin.

3.12. Exception handling

Sponge introduces its own runtime exception defined as a Java class SpongeException. Exception handling in custom Java components (for example plugins) should follow standard Java conventions. Exception handling in scripting knowledge bases should follow standard conventions for the corresponding scripting language.

3.13. Embedding Sponge in custom applications

Sponge may be embedded in a custom Java application using a Maven dependency and the Engine Builder API.

3.13.1. Maven dependency

If you want to use Sponge with, for example, Python scripting knowledge bases, add this dependency to your pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-jython</artifactId>
    <version>1.6.0</version>
</dependency>

There is also a Bill Of Materials style maven artifact for Sponge. Example usage in your pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.openksavi.sponge</groupId>
            <artifactId>sponge-bom</artifactId>
            <version>1.6.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

In that case you may omit the versions of the dependencies.

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-jython</artifactId>
</dependency>

3.14. Integration

3.14.1. Spring framework

Sponge engine can be configured as a Spring bean. That configuration provides standardized access to an embedded Sponge engine for example in J2EE environment.

To provide access to the Spring ApplicationContext in the knowledge base, the SpringPlugin instance should be created, configured as a Spring bean and added to the Sponge engine. The Spring plugin shouldn’t be defined in Sponge XML configuration file.

For more information see the SpringPlugin Javadoc.

Spring Java configuration example
@Configuration
public class TestConfig {

    @Bean
    public Engine spongeEngine() { (1)
        return SpringSpongeEngine.builder().plugin(springPlugin()).knowledgeBase("kb", "examples/spring/spring.py").build(); (2)
    }

    @Bean
    public SpringPlugin springPlugin() { (3)
        return new SpringPlugin();
    }

    @Bean
    public String testBean() {
        return BEAN_VALUE;
    }
}
1 The engine configured as the Spring bean. The SpringSpongeEngine implementation is used here in order to startup and shutdown the engine by Spring. DefaultSpongeEngine could also be used here but it wouldn’t provide automatic startup and shutdown.
2 Added SpringPlugin.
3 SpringPlugin configured as the Spring bean.
Python knowledge base
class SpringTrigger(Trigger):
    def onConfigure(self):
        self.event = "springEvent"
    def onRun(self, event):
        beanValue = spring.context.getBean("testBean") (1)
        self.logger.debug("Bean value = {}", beanValue)
1 A Spring bean named "testBean" is acquired from the Spring ApplicationContext by using SpringPlugin instance referenced by the spring variable.

The SpringSpongeEngine starts up automatically (in the afterPropertiesSet Spring callback method) by default. However it can be configured not to start automatically by setting autoStartup to false.

SpringSpongeEngine not starting automatically example
@Bean
public SpongeEngine spongeEngine() {
    return SpringSpongeEngine.builder().autoStartup(false).plugin(springPlugin()).knowledgeBase("kb", "examples/spring/spring.py").build();
}
3.14.1.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml for this component:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-spring</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.2. Apache Camel

3.14.2.1. Sponge Camel component

The sponge component provides integration bridge between Apache Camel and the Sponge engine. It allows:

  • to route a body of a Camel message to the Sponge engine by converting it to a Sponge event (producer endpoint),

  • to route a message from a Sponge knowledge base to a Camel route (consumer endpoint).

3.14.2.2. Maven configuration

Maven users will need to add the following dependency to their pom.xml for this component:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-camel</artifactId>
    <version>1.6.0</version>
</dependency>
3.14.2.3. URI format
sponge:engineRef[?options]

Where engineRef represents the name of the SpongeEngine implementation instance located in the Camel registry.

3.14.2.4. Options
Name Default value Description

action

"CamelProducerAction"

Could be used only on the producer side of the route. It will synchronously call the Sponge action that has a name specified by the value of this option. However if there is the header named CamelSpongeAction in the Camel In message, it would override the value of this option.

managed

true

If set to true the Sponge engine will be started automatically when the endpoint starts and will be shut down when the endpoint stops.

3.14.2.5. Sponge support for Camel
3.14.2.5.1. CamelPlugin

CamelPlugin provides an interface to the Camel context so it may be used in a knowledge base.

CamelPlugin may be configured in three different ways.

  • Explicitly as a Spring bean and assigned to the engine using the Engine Builder API. This is the preferred way.

    Example
    @Configuration
    public class SpringConfiguration extends SpongeCamelConfiguration {
    
        @Bean
        public SpongeEngine spongeEngine() {
            return SpringSpongeEngine.builder()
                    .config("config.xml")
                    .plugin(camelPlugin())
                    .build();
        }
    }
  • Implicitly when creating a Sponge Camel endpoint.

  • Explicitly in the Sponge XML configuration file.

    Example
    <?xml version="1.0" encoding="UTF-8"?>
    <sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">
    
        <plugins>
            <!-- Note: don't change the plugin name. -->
            <plugin name="camel" class="org.openksavi.sponge.camel.CamelPlugin" />
        </plugins>
    </sponge>
If you use an implicit configuration and you get an error stating that camel variable is not defined, it signifies that a Camel context is not configured yet or Sponge engine is not used in any Camel route.

Only one CamelContext may be used with one instance of Sponge engine, bound by a single CamelPlugin.

Table 26. Important CamelPlugin properties and methods
Property / Method Description

emit(body)

Emits (sends) the body to all current consumers.

producerTemplate

The Camel ProducerTemplate for working with Camel and sending Camel messages.

sendBody(uri, body)

Sends the body to an endpoint. The shortcut for producerTemplate.sendBody(uri, body).

requestBody(uri, body)

Sends the body to an endpoint returning any result output body. The shortcut for producerTemplate.requestBody(uri, body).

getContext()

Returns a Camel context.

getConsumers()

Returns the current list of consumers.

For more information see the CamelPlugin Javadoc.

3.14.2.5.2. Spring-based support

SpongeCamelConfiguration provides base Camel and Sponge configuration using Spring Java configuration. Your Spring configuration could inherit from this class.

Spring bean named "spongeProducerTemplate" allows you to configure a Camel producer template used by CamelPlugin to send Camel messages. If none is present in a Spring configuration, then a default will be used.

Spring bean named springPlugin is the instance of SpringPlugin that could be registered in the engine and used in knowledge bases as the spring variable.

Spring bean named camelPlugin is the instance of CamelPlugin that could be registered in the engine and used in knowledge bases as the camel variable.

3.14.2.6. Producer

Using sponge component on the producer side of the route will forward a body of a Camel message to the specified Sponge engine.

Sponge in a producer mode could be placed in many routes in one Camel context.

Producer example - Spring configuration
@Configuration
public class ExampleConfiguration extends SpongeCamelConfiguration {

    @Bean
    public SpongeEngine spongeEngine() {
        // Use EngineBuilder API to create an engine. Also bind Spring and Camel plugins as beans manually.
        return SpringSpongeEngine.builder()
                .knowledgeBase("camelkb", "examples/camel/camel_producer.py")
                .plugins(springPlugin(), camelPlugin())
                .build();
    }

    @Bean
    public RouteBuilder exampleRoute() {
        return new RouteBuilder() {
            @Override
            public void configure() {
                from("direct:start").routeId("spongeProducer")
                    .to("sponge:spongeEngine");
            }
        };
    }
}
Python knowledge base camel_producer.py
class CamelTrigger(Trigger):
    def onConfigure(self):
        self.event = "spongeProducer"
    def onRun(self, event):
        print event.body
Producer example - Sample code that sends a Camel message
// Starting a Spring context.
GenericApplicationContext context = new AnnotationConfigApplicationContext(ExampleConfiguration.class);
context.start();

// Sending a Camel message.
CamelContext camelContext = context.getBean(CamelContext.class);
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody("direct:start", "Send me to the Sponge");
Output console
Send me to the Sponge
3.14.2.6.1. Camel producer action

Camel producer action will be invoked by Sponge synchronously when a Camel exchange comes to the Sponge engine. The result returned by this action is placed as the body of the Camel IN message. So it can be used by the next endpoint in the route if there is any.

To avoid any misconception please note that events in the Output Event Queue are not sent to the Camel route.
3.14.2.6.2. Default Camel producer action

The default Camel producer action is provided by a Java action CamelProducerAction. If the body of the Camel message is a Sponge event or event definition, than the event is sent to the Sponge immediately. Otherwise this action creates and sends a new event that encapsulates the body. The event is then returned, so it is placed as the body of the Camel In message. The default name of the new event is the name of the corresponding Camel route.

3.14.2.6.3. Custom Camel producer action

You could provide a custom implementation of a Camel producer action in two ways:

  • define your own implementation of CamelProducerAction in a knowledge base,

  • define an action in a knowledge base that takes an instance of Exchange as an argument and specify it in the producer endpoint URI or in the message header, e.g.:

    Python knowledge base
    class CustomAction(Action):
        def onCall(self, exchange):
            return "OK"
    Camel route that sets the action in the endpoint URI
    from("direct:start").routeId("spongeProducer")
            .to("sponge:spongeEngine?action=CustomAction")
            .log("Action result as a body: ${body}");
    Camel route that sets the action in the header
    from("direct:start").routeId("spongeProducer")
            .setHeader("CamelSpongeAction", constant("CustomAction"))
            .to("sponge:spongeEngine)
            .log("Action result as a body: ${body}");
3.14.2.7. Consumer

Using sponge component on the consumer side of the route will forward messages sent from the specified Sponge engine to a Camel route.

Sponge in a consumer mode could be placed in many routes in one Camel context.

Consumer example - Spring configuration
@Configuration
public class ExampleConfiguration extends SpongeCamelConfiguration {

    @Bean
    public SpongeEngine spongeEngine() {
        // Use EngineBuilder API to create an engine. Also bind Spring and Camel plugins as beans manually.
        return SpringSpongeEngine.builder()
                .knowledgeBase("camelkb", "examples/camel/camel_consumer.py")
                .plugins(springPlugin(), camelPlugin())
                .build();
    }

    @Bean
    public RouteBuilder exampleRoute() {
        return new RouteBuilder() {

            @Override
            public void configure() {
                from("sponge:spongeEngine").routeId("spongeConsumer")
                    .log("${body}")
                    .to("stream:out");
            }
        };
    }
}
Python knowledge base camel_simple_consumer.py
class CamelTrigger(Trigger):
    def onConfigure(self):
        self.event = "spongeEvent"
    def onRun(self, event):
        camel.emit(event.get("message"))

    sponge.event("spongeEvent").set("message", "Send me to Camel")

The variable camel is a reference to the instance of CamelPlugin that is associated with the Camel context.

Output console
Send me to Camel

You may also send a message to the Camel endpoint directly, e.g.:

camel.sendBody("direct:log", event.get("message"))

This allows you, for example, to create a flexible message flow using Camel routes and Sponge as a dispatcher.

3.14.2.8. Routes in scripting languages

ScriptRouteBuilder class introduces fromS methods (meaning from Script) that delegate to the corresponding from methods in order to avoid using from since it could be a reserved keyword in scripting languages (e.g. in Python). So when defining Camel routes in Python you should use this class instead of standard RouteBuilder, e.g.:

from org.openksavi.sponge.camel import ScriptRouteBuilder

class PythonRoute(ScriptRouteBuilder):
    def configure(self):
        self.fromS("sponge:spongeEngine").routeId("spongeConsumerCamelPython") \
                .transform().simple("${body}") \
                .process(lambda exchange: sponge.getVariable("receivedRssCount").incrementAndGet()) \
                .to("stream:out")

def onStartup():
    camel.context.addRoutes(PythonRoute())

3.14.3. Sponge REST API server

The Sponge REST API provides users a remote access to the key Sponge functionalities. The REST API server plugin (RestApiServerPlugin) uses Apache Camel REST DSL in order to configure the JSON/REST service. After starting the plugin, the online API specification in the Swagger JSON format will be available at URL http://localhost:1836/api-doc.

The default name of the REST API plugin (which can be used in knowledge bases) is restApiServer.

Table 27. Key REST API plugin configuration parameters
Name Type Description

autoStart

boolean

If true then the REST service will start when the plugin starts up. Defaults to true.

restComponentId

String

The Camel REST component id. Defaults to "jetty".

host

String

The REST API host.

port

int

The REST API port. Defaults to 1836.

prettyPrint

boolean

The pretty print option. Defaults to false.

publicActions

List<ProcessorQualifiedName>

Public actions.

publicEvents

List<String>

Public event names.

sslConfiguration

SslConfiguration

The SSL configuration.

publishReload

boolean

If true then the reload operation will be published. Defaults to false.

routeBuilderClass

String

The name of the class extending RestApiRouteBuilder (which is the default route builder).

apiServiceClass

String

The RestApiService implementation class name. Defaults to DefaultRestApiService.

securityServiceClass

String

The RestApiSecurityService implementation class name. Defaults to NoSecuritySecurityService.

authTokenServiceClass

String

The RestApiAuthTokenService implementation class name. The default implementation is JwtRestApiAuthTokenService that uses JSON Web Token (JWT) for Java JJWT library.

authTokenExpirationDurationSeconds

Long

The duration (in seconds) after which an authentication token will expire. Defaults to null which means infinity.

The REST API server plugin XML configuration example
<sponge>
    <plugins>
        <plugin name="restApiServer" class="org.openksavi.sponge.restapi.server.RestApiServerPlugin">
            <configuration>
                <port>1836</port>
                <autoStart>false</autoStart>
            </configuration>
        </plugin>
    </plugins>
</sponge>
The REST API server plugin Java configuration example
@Configuration
public static class Config extends SpongeCamelConfiguration {

    @Bean
    public SpongeEngine spongeEngine() {
        return SpringSpongeEngine.builder().plugins(camelPlugin(), restApiPlugin())
                .config("sponge_config.xml").build();
    }

    @Bean
    public RestApiServerPlugin restApiPlugin() {
        RestApiServerPlugin plugin = new RestApiServerPlugin();
        plugin.setSecurityService(restApiSecurityService());

        return plugin;
    }

    @Bean
    public RestApiSecurityService restApiSecurityService() {
        return new SimpleInMemorySecurityService();
    }
}

For more information see the RestApiServerPlugin Javadoc.

The REST API plugin provides a simple predefined knowledge base libraries: engine_admin_library.py and engine_public_library.py.

Example use of the REST API predefined knowledge base library
<knowledgeBase name="security" displayName="Security">
    <file>classpath:org/openksavi/sponge/restapi/server/engine_admin_library.py</file>
</knowledgeBase>
3.14.3.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-rest-api-server</artifactId>
    <version>1.6.0</version>
</dependency>

Depending on the REST Camel component, you should add a corresponding dependency, e.g. camel-jetty for Jetty, camel-servlet for a generic servlet. For more information see the Camel documentation.

3.14.3.2. Operations summary

The following table contains a summary of the REST API operations. For a complete list of operations see the specification generated using Swagger and Swagger2Markup.

Table 28. The REST API operations summary
Name URI Description

Get the Sponge version

version

Returns the Sponge version.

Login

login

User login. Used in a token-based authentication scenario.

Logout

logout

User logout. Used in a token-based authentication scenario.

Get knowledge bases

knowledgeBases

Returns the knowledge bases which the user may use (i.e. may call actions registered in these knowledge basses).

Get actions

actions

Returns the metadata of actions that are available to the user. If you want to get metadata for specified actions, set the request property name to an action name or a Java-compatible regular expression. If you want to get only actions that have argument and result metadata specified in their configuration, set the request property metadataRequired to true (defaults to false).

Call an action

call

Calls an action.

Provide action arguments

actionArgs

Returns provided arguments, i.e. values along with value sets of action arguments. The request accepts the following properties: name - the action name, argNames - the optional list of argument names that are to be provided (if null, all provided arguments will be produced), current - the optional map of argument names and their current values passed from a client code.

Send a new event

send

Sends a new event.

Reload knowledge bases

reload

Reloads all knowledge bases. This operation may not be published. It should be available only to administrators.

3.14.3.3. JSON/Java mapping

The REST API uses the Jackson library to process JSON. A transformation of action arguments and result values is determined by types specified in the corresponding action arguments and result metadata.

The default Jackson configuration for the REST API sets the ISO8601 format for dates.

A BinaryType value is marshalled to a base64 encoded string. This encoding adds significant overhead and should be used only for relatively small binary data.
3.14.3.4. Requests and responses

Each request may contain base properties.

Table 29. Base request properties
Name Required Description

id

No

A request identifier. If it is present, the response will contain the same id property with the same value. This feature provides some compatibility with the JSON-RPC protocol.

username

No

A user name that may be used in a user/password authentication mode. In that case, if there is no user name present, the anonumous user is assumed.

password

No

A user password that may be used in a user/password authentication mode.

authToken

No

An authentication token that may be used in a token-based authentication mode.

Examples of REST API requests
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/version
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/knowledgeBases
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/actions
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/actions -d '{"username":"john","password":"password"}'
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/actions -d '{"name":".*Case"}'
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/call -d '{"name":"UpperCase","args":["test1"]}'
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/send -d '{"name":"alarm","attributes":{"a1":"test1","a2":"test2", "a3":4}}'
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/sponge.json/v1/reload
3.14.3.5. Security

The REST API provides only simple security out of the box and only if turned on. All requests allow passing a user name and a password. If the user name is not set, the anonymous user is assumed. A user may have roles.

You may set a security strategy by providing an implementation of the RestApiSecurityService interface. You may find a few examples of such implementations in the source code. In production mode we suggest using Spring Security and configure Camel security. An advanced security configuration has to be set up in Java rather than in a Sponge XML configuration file. You may implement various authorization scenarios, for example using HTTP headers that are available in a Camel exchange.

3.14.3.5.1. Authentication mode

Only a username/password authentication mode is currently supported by the default REST API service implementation.

Table 30. Authentication modes
Name Description

Username/password

Every request has to contain a username and a password. Invoking the login operation switches to the authentication token mode.

Authentication token

Every request has to contain an authentication token, returned by the login operation. It may not contain neither username nor password.

3.14.3.5.2. Simple security strategy

The simple security strategy uses in-memory user data. User privileges and access to knowledge bases, actions and events are verified by calling Sponge actions (RestApiIsActionPublic, RestApiIsEventPublic, RestApiCanUseKnowledgeBase, RestApiCanSendEvent). Passwords are stored as SHA-256 hashes.

Example of the REST API simple security
from org.openksavi.sponge.restapi.server.security import User

# Simple access configuration: role -> knowledge base names regexps.
ROLES_TO_KB = { "admin":[".*"], "guest":["mpd"], "anonymous":["mpd"]}

class RestApiCanUseKnowledgeBase(Action):
    def onCall(self, user, kbName):
        return restApiServer.canUseKnowledgeBase(ROLES_TO_KB, user, kbName)

def onStartup():
    # Set up users. To hash a password use (on Mac): echo -n <username><password> | shasum -a 256 | awk '{ print $1 }'
    # Note that the user name must be lower case.
    securityService = restApiServer.service.securityService
    securityService.addUser(User("john", "4ae0aa2783d6e8a939b0a3ce8146400001ef6b9840958aea136b416c58a2f9b8", ["admin"]))
    securityService.addUser(User("joe", "e52abe94e1e06f794a3654e02bfbe71565025ea6ce2898d1ad459182f3bc147b", ["guest"]))

For more information see examples in the source code.

3.14.3.6. HTTPS

In production mode you should configure HTTPS, preferably using a signed certificate. Otherwise your passwords could be sent in plain text over the network as a part of the REST API JSON requests.

3.14.3.6.1. Sponge REST API
Overview
Version information

Version : 1

URI scheme

Host : localhost:1836
BasePath : /
Schemes : HTTP

Tags
  • sponge.json/v1 : Sponge REST API

Paths
Provide action arguments
POST sponge.json/v1/actionArgs
Parameters
Type Name Description Schema

Body

body
required

The provide action arguments request

Responses
HTTP Code Description Schema

200

The provide action arguments response

Tags
  • sponge.json/v1

Get actions
POST sponge.json/v1/actions
Parameters
Type Name Description Schema

Body

body
required

Get actions request

Responses
HTTP Code Description Schema

200

The get actions response

Tags
  • sponge.json/v1

Call an action
POST sponge.json/v1/call
Parameters
Type Name Description Schema

Body

body
required

Call action request

Responses
HTTP Code Description Schema

200

The action call response

Tags
  • sponge.json/v1

Get knowledge bases
POST sponge.json/v1/knowledgeBases
Parameters
Type Name Description Schema

Body

body
required

Get knowledge bases request

Responses
HTTP Code Description Schema

200

The get knowledge bases response

Tags
  • sponge.json/v1

Login
POST sponge.json/v1/login
Parameters
Type Name Description Schema

Body

body
required

Login request

Responses
HTTP Code Description Schema

200

The login response

Tags
  • sponge.json/v1

Logout
POST sponge.json/v1/logout
Parameters
Type Name Description Schema

Body

body
required

Logout request

Responses
HTTP Code Description Schema

200

The logout response

Tags
  • sponge.json/v1

Reload knowledge bases
POST sponge.json/v1/reload
Parameters
Type Name Description Schema

Body

body
required

Reload knowledge bases request

Responses
HTTP Code Description Schema

200

The reload response

Tags
  • sponge.json/v1

Send a new event
POST sponge.json/v1/send
Parameters
Type Name Description Schema

Body

body
required

Send event request

Responses
HTTP Code Description Schema

200

The send event response

Tags
  • sponge.json/v1

Get the Sponge version
POST sponge.json/v1/version
Parameters
Type Name Description Schema

Body

body
required

Get Sponge version request

Responses
HTTP Code Description Schema

200

The Sponge version response

Tags
  • sponge.json/v1

Definitions
ProvideActionArgsRequest

A provide action arguments request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

name
required

The action name

string

argNames
optional

The names of action arguments to provide

< string > array

current
optional

The current values of action arguments in a client code

< string, object > map

version
optional

The expected knowledge base version

integer (int32)

ProvideActionArgsResponse

A provide action arguments response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

provided
required

The provided action arguments

< string, ArgValueObject > map

ArgValue
Name Schema

value
optional

object

valuePresent
optional

boolean

valueSet
optional

< object > array

valueSetDisplayNames
optional

< string > array

ArgValueObject
Name Schema

value
optional

object

valuePresent
optional

boolean

valueSet
optional

< object > array

valueSetDisplayNames
optional

< string > array

GetActionsRequest

A get actions request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

name
optional

The action name or the regular expression (compatible with https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)

string

metadataRequired
optional

The metadata required flag

boolean

KnowledgeBaseMeta

A knowledge base metadata

Name Description Schema

name
required

The knowledge base name

string

displayName
optional

The knowledge base display name

string

description
optional

The knowledge base description

string

version
optional

The knowledge base version

integer (int32)

ActionMeta

An action metadata

Name Description Schema

name
required

The action name

string

displayName
optional

The action display name

string

description
optional

The action description

string

knowledgeBase
required

The action knowledge base metadata

features
required

The action features

< string, object > map

argsMeta
optional

The action arguments metadata

< ActionArgMeta > array

resultMeta
optional

The action result metadata

ActionArgMeta

An action argument metadata

Name Description Schema

name
required

The action argument name

string

type
required

The action argument type

displayName
optional

The action argument display name

string

description
optional

The action argument description

string

optional
optional

Optional argument

boolean

provided
optional

Provided argument

boolean

depends
optional

Argument depends on others arguments

< string > array

readOnly
optional

Read only argument

boolean

overwrite
optional

The overwrite flag

boolean

GetActionsResponse

A get actions response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

actions
required

The available actions

< ActionMeta > array

DataType
Name Schema

kind
optional

enum (STRING, NUMBER, INTEGER, BOOLEAN, BINARY, ANY, VOID, OBJECT, LIST, MAP, ANNOTATED)

format
optional

string

defaultValue
optional

object

nullable
optional

boolean

features
optional

< string, object > map

ActionResultMeta

An action result metadata

Name Description Schema

type
required

The action result type

displayName
optional

The action result display name

string

description
optional

The action result description

string

ActionCallRequest

An action call request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

name
required

The action name

string

args
optional

The action arguments

< object > array

version
optional

The expected knowledge base version

integer (int32)

ActionCallResponse

An action call response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

result
required

The action result

object

GetKnowledgeBasesRequest

A get knowledge bases request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

GetKnowledgeBasesResponse

A get knowledge bases response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

knowledgeBases
required

The available knowledge bases

< KnowledgeBaseMeta > array

LoginRequest

A login request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

LoginResponse

A login response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

authToken
required

The authentication token

string

LogoutRequest

A logout request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

LogoutResponse

A logout response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

ReloadRequest

A reload request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

ReloadResponse

A reload response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

SendEventRequest

A send event request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

name
required

The event name

string

attributes
optional

The event attributes

< string, object > map

SendEventResponse

A send event response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

eventId
required

The event id

string

GetVersionRequest

A get version request

Name Description Schema

id
optional

The request id

string

username
optional

The user name

string

password
optional

The user password

string

authToken
optional

The authentication token

string

GetVersionResponse

A get version response

Name Description Schema

id
optional

The corresponding request id

string

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

version
required

The Sponge version

string

3.14.3.7. Environment
3.14.3.7.1. Standalone

This is the default configuration that uses the embedded Jetty server.

3.14.3.7.2. Servlet container

The Sponge REST API service may also be deployed into a servlet container (e.g. Tomcat) as a web application. See the REST API Demo Service example.

3.14.4. Sponge REST API client for Java

The Sponge REST API client for Java simplifies connecting to a remote Sponge REST API server from applications written in Java. The default implementation uses the OkHttp library.

REST API client for an anonymous user
try (SpongeRestClient client = new DefaultSpongeRestClient(SpongeRestClientConfiguration.builder()
        .url("http://localhost:8080/sponge.json/v1")
        .build())) { (1)
    String upperCaseText = client.call(String.class, "UpperCase",  Arrays.asList("text")); (2)
}
1 Create a new REST API client.
2 Call the remote action.
REST API client for a named user
SpongeRestClient client = new DefaultSpongeRestClient(SpongeRestClientConfiguration.builder()
        .url(String.format("http://localhost:%d/%s", PORT, RestApiConstants.DEFAULT_PATH))
        .username(username)
        .password(password)
        .build());

DefaultSpongeRestClient performs best when you create a single instance and reuse it for all of your REST API calls.

For more information see the DefaultSpongeRestClient Javadoc and examples in the source code.

3.14.4.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-rest-api-client</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.5. Sponge REST API client for Dart

The Sponge REST API client for Dart simplifies connecting to a remote Sponge REST API service from applications written in Dart. It could be used in a Flutter mobile application or an AngularDart web application to connect to a Sponge based back-end.

REST API client example
import 'package:sponge_client_dart/sponge_client_dart.dart';

void main() async {
  // Create a new client for an anonymous user.
  var client = SpongeRestClient(
      SpongeRestClientConfiguration('http://localhost:8888/sponge.json/v1'));

  // Get the Sponge server version.
  var version = await client.getVersion();
  print('Sponge version: $version.');

  // Get actions metadata.
  List<ActionMeta> actionsMeta = await client.getActions();
  print('Available action count: ${actionsMeta.length}.');

  // Call the action with arguments.
  String upperCaseText = await client.call('UpperCase', ['Text to upper case']);
  print('Upper case text: $upperCaseText.');

  // Send a new event to the Sponge engine.
  var eventId = await client.send('alarm',
      attributes: {'source': 'Dart client', 'message': 'Something happened'});
  print('Sent event id: $eventId.');

  // Create a new client for a named user.
  client = SpongeRestClient(
    SpongeRestClientConfiguration('http://localhost:8888/sponge.json/v1')
      ..username = 'john'
      ..password = 'password',
  );
}

Unless noted otherwise in the release notes, versions of the REST API client for Dart that have the same major.minor numbers as the Sponge service are compatible.

The REST API client for Dart is published at pub.dartlang.org.

For more information see the {dartClientDartdoc}/SpongeRestClient-class.html[SpongeRestClient Dartdoc] and the project {dartClientSourcesUrl}/[source code].

The example of using the REST API client for Dart in the AngularDart web application is hosted at https://github.com/softelnet/sponge_client_angular_dart_example.

3.14.6. Running external processes

Sponge provides the ProcessInstance API to run an external executable as a subprocess of the Sponge Java process. This feature is used by some of the plugins, for example by the Py4J integration plugin to execute an external Python script.

In general, an external process can be executed using:

  • Sponge ProcessInstance API (covered in this chapter),

  • scripting language API,

  • Apache Camel exec component,

  • Java API (ProcessBuilder).

Table 31. Subprocess configuration parameters
Name Type Description

name

String

The process display name.

executable

String

The process executable.

argument

String

Zero or more process arguments.

workingDir

String

The process working directory. If null (the default value) then the current directory will be used.

env

name, value

Zero or more additional environment variables for the subprocess.

waitSeconds

Long

The maximum number of seconds to wait after the start of the process. The thread that started the process will be blocked until the time elapses or the subprocess exits. If null (the default value), the thread will not wait.

inputRedirect

InputRedirect

The standard input redirect type (see the following tables). There are convenience methods inputAs…​ available.

outputRedirect

OutputRedirect

The standard output redirect type (see the following tables). There are convenience methods outputAs…​ available.

errorRedirect

ErrorRedirect

The standard error redirect type (see the following tables). There are convenience methods errorAs…​ available.

charset

String

The the charset of the subprocess streams used if the redirect type is STRING.

waitForPositiveLineRegexp

String

The Java regular expression of a line from the process output text stream. The thread that started the process will wait (blocking) for such line. If set to null, the thread will not wait for a specific line (or waitForNegativeLineRegexp if set).

waitForNegativeLineRegexp

String

Sets the Java regular expression of a line from the process output text stream that signals an error and should cause throwing an exception.

waitForLineTimeout

Long

The timeout for waiting for a specific line from the process output stream (in seconds). If null, the thread could wait indefinitely. If the timeout is exceeded, the exception will be thrown.

inputString

String

The input string that will be set as the process standard input. Applicable only if the input redirect type is STRING.

inputBinary

byte[]

he input bytes that will be set as the process standard input. Applicable only if the input redirect type is BINARY.

Table 32. Standard input redirect type
Value Description

PIPE

Indicates that subprocess standard input will be connected to the current Java process over a pipe. This is the default handling of subprocess standard input.

INHERIT

Sets the destination for subprocess standard input to be the same as those of the current Java process.

STRING

Sets the subprocess input as the ProcessConfiguration.inputString string.

BINARY

Sets the subprocess input as the ProcessConfiguration.inputBinary bytes.

FILE

Sets the subprocess input as the ProcessConfiguration.inputFile file specified as the file name.

STREAM

Sets the subprocess input as a stream. This is a special case of PIPE that makes easier writing to and closing the subprocess standard input ProcessInstance.getInput() after start. Then you should invoke manually ProcessInstance.waitForReady().

Table 33. Standard output redirect type
Value Description

PIPE

Indicates that subprocess standard output will be connected to the current Java process over a pipe. This is the default handling of subprocess standard output.

INHERIT

Sets the destination for subprocess standard output to be the same as those of the current Java process.

STRING

Writes all subprocess standard output to the ProcessInstance.outputString string. The thread that started the subprocess will wait for the subprocess to exit.

BINARY

Writes all subprocess standard output to the ProcessInstance.outputBinary byte array. The thread that started the subprocess will wait for the subprocess to exit.

FILE

Writes all subprocess standard output to the ProcessInstance.outputFile file. The thread that started the subprocess will wait for the subprocess to exit.

CONSUMER

Sends a subprocess standard output as text lines to a line consumer (if set). It also logs the subprocess standard output to the logger (as INFO).

Table 34. Standard error redirect type
Value Description

PIPE

Indicates that subprocess error output will be connected to the current Java process over a pipe. This is the default handling of subprocess error output.

INHERIT

Sets the destination for subprocess error output to be the same as those of the current Java process.

STRING

Writes all subprocess error output to the ProcessInstance.errorString string. The thread that started the subprocess will wait for the subprocess to exit.

FILE

Writes all subprocess error output to the ProcessInstance.getErrorFile file. The thread that started the subprocess will wait for the subprocess to exit.

EXCEPTION

Throw an exception if the error output is not empty. The thread that started the subprocess will wait for the subprocess to exit.

CONSUMER

Sends a subprocess standard error as text lines to a line consumer (if set). It also logs the subprocess error output to the logger (as WARN).

Example of running an external executable with arguments in Java
ProcessInstance process = engine.getOperations().process(ProcessConfiguration.builder("echo", "TEST").outputAsString()).run();
String output = process.getOutputString();
Example of running an external executable with arguments in Python
from org.openksavi.sponge.util.process import ProcessConfiguration

process = sponge.process(ProcessConfiguration.builder("echo", "TEST").outputAsString()).run()
print process.outputString
Example of running an external executable with an additional environment in Java
ProcessInstance process = engine.getOperations().process(ProcessConfiguration.builder("printenv")
        .arguments("TEST_VARIABLE").env("TEST_VARIABLE", "TEST").outputAsString()).run();
Example of running an external executable with an additional environment in Python
from org.openksavi.sponge.util.process import ProcessConfiguration

process = sponge.process(ProcessConfiguration.builder("printenv").arguments("TEST_VARIABLE")
                .env("TEST_VARIABLE", "TEST").outputAsString()).run()

For more examples see ProcessInstanceTest.java.

3.14.7. Python (CPython) / Py4J

Sponge may communicate with external programs written in the reference implementation of the Python programming language - CPython using Py4J, and vice versa. A Python program and a Sponge Java process communicate through network sockets.

Py4J by default uses the TCP port 25333 to communicate from Python to Java and TCP port 25334 to communicate from Java to Python.

There is no support for writing knowledge bases in CPython.

In the following examples Python 3 will be used.

The CPython environment must have Py4J installed, e.g.:

pip3 install py4j

For more information on Py4J see https://www.py4j.org/advanced_topics.html.

3.14.7.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-py4j</artifactId>
    <version>1.6.0</version>
</dependency>
3.14.7.2. Py4J plugins

Sponge provides two plugins for integration with CPython.

Local network sockets used by Py4j should be secured, for example using TLS. Please be aware that all Sponge operations are accessible in other processes that communicate with the Sponge with Py4J enabled by a plugin. See https://github.com/softelnet/sponge/tree/master/sponge-py4j/examples/py4j//java_server_tls for an example of TLS security, based on Py4J examples. Note that in a production environment you should customize this simple configuration, possibly by providing your own configured instance of GatewayServer or ClientServer to the plugin.
Table 35. Py4J plugin common configuration parameters
Name Type Description

facadeInterface

String

A Java interface that is a facade to the Py4J entry point object configured on the CPython side.

javaPort

int

Java side server port.

pythonPort

int

CPython side server port.

security

XML element/SslConfiguration

The simple SSL security configuration.

security/keyStore

String

Simple security keystore file location on the classpath.

security/keyStorePassword

String

Simple security keystore password.

security/keyPassword

String

Simple security key password.

security/algorithm

String

Simple security algorithm. The default value is SunX509.

pythonScript

XML element/ProcessConfiguration

The configuration of the CPython script that can be run as a subprocess of the Sponge Java process when the plugin is starting up. Typically such script would init the Py4J connection on the CPython side. The plugin automatically adds to the environment variables for the subprocess: PY4J_JAVA_PORT, PY4J_PYTHON_PORT and optionally PY4J_AUTH_TOKEN.

pythonScriptBeforeStartup

boolean

If true, the CPython script will be started before this plugin startup (the default value), otherwise it will be started after this plugin startup.

generateAuthToken

boolean

If true, the plugin will generate the Py4J auth token (for both sides). The default value is false. This option is useful when combined with the pythonScript.

authToken

String

The manual or generated Py4J auth token (for both sides).

randomPorts

boolean

If true, the plugin will use random ports (for both sides). The default value is false. This option is useful when combined with the pythonScript.

3.14.7.2.1. GatewayServerPy4JPlugin

GatewayServerPy4JPlugin provides integration with CPython using Py4J GatewayServer.

For more information see the GatewayServerPy4JPlugin Javadoc.

Sponge side example
GatewayServerPy4JPlugin XML configuration example
<sponge>
    <plugins>
        <plugin name="py4j" class="org.openksavi.sponge.py4j.GatewayServerPy4JPlugin" />
    </plugins>
</sponge>
CPython side example
Sending Sponge event in CPython
from py4j.java_gateway import JavaGateway

gateway = JavaGateway()

# The Sponge in other process accessed via Py4J
sponge = gateway.entry_point

print "Connected to {}".format(sponge.getInfo())
sponge.event("helloEvent").set("say", "Hello from Python's Py4J").send()

Note that a simplified bean property access is not supported here. So instead of sponge.info you have to invoke sponge.getInfo().

3.14.7.2.2. ClientServerPy4JPlugin

ClientServerPy4JPlugin provides integration with CPython using Py4J ClientServer.

Table 36. ClientServerPy4JPlugin plugin specific configuration parameters
Name Type Description

autoStartJavaServer

Boolean

Auto start of Py4J JavaServer.

For more information see the ClientServerPy4JPlugin Javadoc.

Sponge side example
ClientServerPy4JPlugin XML configuration example
<sponge>
    <plugins>
        <plugin name="py4j" class="org.openksavi.sponge.py4j.ClientServerPy4JPlugin">
            <configuration>
                <facadeInterface>org.openksavi.sponge.py4j.PythonService</facadeInterface>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Python facade interface
public interface PythonService {
    String toUpperCase(String text);
}
ClientServerPy4JPlugin knowledge base example written in Jython
# Note that this code is interpreted by Jython in Sponge, not CPython
class PythonUpperCase(Action):
    def onCall(self, text):
        result = py4j.facade.toUpperCase(text)
        self.logger.debug("CPython result for {} is {}", text, result)
        return result
CPython side example
Implementation of the facade interface in CPython
from py4j.clientserver import ClientServer

class PythonService(object):
    def toUpperCase(self, text):
        return text.upper()
    class Java:
        implements = ["org.openksavi.sponge.py4j.PythonService"]

pythonService = PythonService()
gateway = ClientServer(python_server_entry_point=pythonService)
3.14.7.3. Executing an external Python script

The plugin may run a CPython script as a subprocess.

Example of an XML configuration for executing an external Python script
    <plugins>
        <plugin name="py4j" class="org.openksavi.sponge.py4j.GatewayServerPy4JPlugin">
            <configuration>
                <pythonScript>
                    <executable>python3</executable>
                    <argument>${sponge.configDir}/cpython_script.py</argument>
                    <waitSeconds>60</waitSeconds>
                    <waitForOutputLineRegexp>The CPython service has started.</waitForOutputLineRegexp>
                    <outputRedirect>CONSUMER</outputRedirect>
                </pythonScript>
                <pythonScriptBeforeStartup>false</pythonScriptBeforeStartup>
            </configuration>
        </plugin>
    </plugins>

3.14.8. ReactiveX

The ReactiveX plugin (ReactiveXPlugin) provides support for using ReactiveX in knowledge bases, e.g. for processing stream of Sponge events using reactive programming. The plugin uses RxJava library. The current version of the plugin is very simple. For example it hasn’t got any configuration parameters.

The default name of the ReactiveX plugin (which can be used in knowledge bases) is rx.

The main object provided by this plugin is an instance of a hot observable (rx.observable) that emits all non system Sponge events. The plugin registers a Java-based correlator that listens to Sponge events and sends them to the observable.

For more information see the ReactiveXPlugin Javadoc.

The following example shows how to use reactive programming in a Sponge knowledge base.

Example Python knowledge base - Reactive programming
import time
from io.reactivex.schedulers import Schedulers

def onStartup():
    sponge.event("e1").set("payload", 1).send()
    sponge.event("e2").set("payload", 2).sendAfter(500)
    sponge.event("e3").set("payload", 3).sendAfter(1000)

    rx.observable.subscribe(lambda event: sponge.logger.info("{}", event.name))

    def observer(event):
        time.sleep(1)
        sponge.logger.info("After sleep: {}", event.name)
    rx.observable.observeOn(Schedulers.io()).subscribe(observer)
Example XML configuration
<sponge>
    <knowledgeBases>
        <knowledgeBase name="kb">
            <file>reactivex.py</file>
        </knowledgeBase>
    </knowledgeBases>
    <plugins>
        <plugin name="rx" class="org.openksavi.sponge.reactivex.ReactiveXPlugin" />
    </plugins>
</sponge>
3.14.8.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-reactivex</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.9. MIDI

The MIDI plugin (MidiPlugin) allows processing MIDI messages by the Sponge and provides communication with MIDI devices. It wraps MIDI messages in Sponge events. The plugin supports ShortMessage, MetaMessage and SysexMessage MIDI messages wrapping them respectively in MidiShortMessageEvent, MidiMetaMessageEvent and MidiSysexMessageEvent Sponge events. Although the MIDI support in the Sponge provides a set of methods that use the javax.sound.midi API, the goal of this plugin is not to be a complete interface to the MIDI system but a bridge between MIDI messages and Sponge events.

The default name of the MIDI plugin (which can be used in knowledge bases) is midi.

Table 37. MIDI plugin configuration parameters
Name Type Description

sequencerConnectedToSynthesizer

Boolean

If true then the default MIDI sequencer will be connected to the default synthesizer (e.g. to generate sound while playing MIDI files). The default value is false.

loadAllInstruments

Boolean

If true then all instruments in the default soundbank will be loaded at startup. The default value is true.

midiShortMessageEventName

String

A name of a MIDI ShortMessage Sponge event sent by this plugin to the engine. The default value is "midiShort".

midiMetaMessageEventName

String

A name of a MIDI MetaMessage Sponge event sent by this plugin to the engine. The default value is "midiMeta".

midiSysexMessageEventName

String

A name of a MIDI SysexMessage Sponge event sent by this plugin to the engine. The default value is "midiSysex".

For more information see the MidiPlugin Javadoc.

Example Python knowledge base that shows how to process MIDI messages created by an external MIDI input device
from javax.sound.midi import ShortMessage
from org.openksavi.sponge.midi import MidiUtils

class SameSound(Trigger):
    def onConfigure(self):
        self.event = "midiShort" (1)
    def onRun(self, event):
        midi.sound(event.message) (2)

class Log(Trigger):
    def onConfigure(self):
        self.event = "midiShort"
    def onRun(self, event):
        self.logger.info("{}Input message: {}", "[" + MidiUtils.getKeyNote(event.data1) + "] " if event.command == ShortMessage.NOTE_ON else "",
                         event.messageString) (3)

def onStartup():
    sponge.logger.info("This example program enables a user to play an input MIDI device (e.g. a MIDI keyboard) using the Sponge MIDI plugin.")
    midi.connectDefaultInputDevice() (4)
    sponge.logger.info("Input MIDI device: {}", midi.inputDevice.deviceInfo.name)
    sponge.logger.info("Instruments: {}", ",".join(list(map(lambda i: i.name + " (" + str(i.patch.bank) + "/" + str(i.patch.program) + ")", midi.instruments))))
    midi.setInstrument(0, "Electric Piano 1") (5)
1 The trigger SameSound listens to all MIDI short messages.
2 The trigger SameSound sends all MIDI short messages received from the input MIDI device to the MIDI synthesizer to generate sounds. It is achieved through the use of the sound method in the midi plugin.
3 The trigger Log only logs a MIDI message info and a note for note on MIDI messages.
4 Connects a default input MIDI device in the system (e.g. a MIDI keyboard) to the MIDI plugin in order to receive all MIDI messages generated by this device and send them to the Sponge engine as Sponge events.
5 Sets the instrument (by name) in the MIDI synthesizer for the MIDI channel 0. Note that this example assumes that the input MIDI device will generate MIDI messages for the same channel.
An event flow in the Sponge engine introduces an additional performance overhead that in some situations may be not acceptable when dealing with real-time physical MIDI instruments.
3.14.9.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-midi</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.10. MPD

The MPD plugin (MpdPlugin) provides an integration with an MPD (Music Player Daemon) server. The integration allows knowledge bases to manage an MPD server and gives the possibility to process MPD based events.

The plugin is located in the sponge-mpd Sponge external module. The reason that the MPD support is located in the external Sponge module is that it is released under GNU GPL Version 3 license which is not compatible with the Sponge Apache 2.0 license. This requirement is forced by the license of the underlying library JavaMPD.

The default name of the MPD plugin (which can be used in knowledge bases) is mpd.

The MPD plugin provides a simple predefined knowledge base library.

Example use of the MPD predefined knowledge base library
<knowledgeBase name="mpd" displayName="MPD">
    <file>classpath*:org/openksavi/sponge/mpd/mpd_library.py</file>
</knowledgeBase>
Table 38. MPD plugin configuration parameters
Name Type Description

hostname

String

The MPD server hostname.

port

Integer

The MPD server port.

password

String

The MPD server password.

timeout

Integer

The MPD server timeout.

eventNamePrefix

String

The MPD-based Sponge event name prefix. The default value is "mpd".

autoConnect

Boolean

The auto connect flag. If true (the default value), the plugin connects to the MPD server on startup.

autoStartMonitor

Boolean

The auto start monitor flag. If true (the default value), the plugin starts the MPD monitor. The MPD monitor allows the plugin to send MPD based events to the engine.

For more information see the MpdPlugin Javadoc and JavaMPD.

The following example searches for albums in the MPD database matching the given criteria and adds them to the playlist.

Example Python knowledge base
def selectAlbums(albums, yearMin, yearMax, genreRegexp):
    selectedAlbums = []
    for album in albums:
        if album.date and album.date >= str(yearMin) and album.date <= str(yearMax) and album.genre and re.match(genreRegexp.lower(), album.genre.lower()):
            selectedAlbums.append(album)
    return selectedAlbums

def setAndPlayPlaylist(albums):
    if len(albums) == 0:
        return
    mpd.server.playlist.clearPlaylist() (1)
    mpd.server.playlist.insertAlbum(albums[0])
    # Play immediately after inserting the first album
    mpd.server.player.play()
    for album in albums[1:]:
        mpd.server.playlist.insertAlbum(album)

def onStartup():
    albums = mpd.server.musicDatabase.albumDatabase.listAllAlbums()
    sponge.logger.info("MPD server version: {}. All album count: {}", mpd.server.version, len(albums))

    sponge.logger.info("Setting the playlist...")
    # Set the playlist to rock albums released since 1970
    selectedAlbums = selectAlbums(albums, 1970, None, ".*(Rock).*")
    if len(selectedAlbums) > 0:
        setAndPlayPlaylist(selectedAlbums)
        sponge.logger.info("The playlist is set, {} albums found", len(selectedAlbums))
    else:
        sponge.logger.info("No matching albums found")
1 The mpd.server is a reference to the instance of the MPD class (part of JavaMPD library) which represents a connection to a MPD server.
3.14.10.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-mpd</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.11. Raspberry Pi - Pi4J

The Pi4J plugin (Pi4JPlugin) allows using the Pi4J library in Sponge knowledge bases. The Pi4J library provides a friendly object-oriented I/O API and implementation libraries to access the full I/O capabilities of the Raspberry Pi platform. The current version of the plugin is very simple. For example it hasn’t got any configuration parameters.

The default name of the Pi4J plugin (which can be used in knowledge bases) is pi4j.

For more information see the Pi4JPlugin Javadoc.

The following example shows how to turn on/off a Grove LED connected to the Raspberry Pi GPIO. The hardware setup for this example includes Raspberry Pi 3, a ribbon cable, a ribbon cable socket, a breadboard, a 4-pin male jumper to Grove 4 pin conversion cable and a Grove LED. Before setting up the hardware make sure that your Raspberry Pi is not powered! The Grove LED should be connected to GPIO via a 4-pin connector: the black wire goes on PIN#14 (Ground), the red wire goes on PIN#02 (DC Power 5V), the yellow wire goes on PIN#12 (GPIO18/GPIO_GEN1), the white wire goes on PIN#06 (Ground).

Example Python knowledge base - Blinking LED
from com.pi4j.io.gpio import RaspiPin, PinState

state = False

class LedBlink(Trigger):
    def onConfigure(self):
        self.event = "blink"
    def onRun(self, event):
        global led, state
        state = not state
        led.setState(state)

def onStartup():
    global led
    led = pi.gpio.provisionDigitalOutputPin(RaspiPin.GPIO_01, "led", PinState.LOW)
    sponge.event("blink").sendAfter(0, 1000)

def onShutdown():
    off()

on = lambda: led.setState(True)
off = lambda: led.setState(False)
Example XML configuration
<sponge>
    <properties>
        <!-- Due to the problem https://github.com/Pi4J/pi4j/issues/319, the dynamic linking option is turned on, where Pi4J is dynamically linked
            to WiringPi rather than the default static linking. -->
        <property name="pi4j.linking" system="true">dynamic</property>
    </properties>
    <knowledgeBases>
        <knowledgeBase name="kb">
            <file>pi4j_led_blink.py</file>
        </knowledgeBase>
    </knowledgeBases>
    <plugins>
        <plugin name="pi" class="org.openksavi.sponge.rpi.pi4j.Pi4JPlugin" />
    </plugins>
</sponge>
3.14.11.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-rpi-pi4j</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.12. Raspberry Pi - GrovePi

The GrovePi plugin (GrovePiPlugin) allows accessing the GrovePi hardware in Sponge knowledge bases. GrovePi is an electronics board for Raspberry Pi that may have a variety of sensors and actuators connected to. The plugin uses Java 8 GrovePi library. The current version of the plugin is very simple. For example it hasn’t got any configuration parameters.

The default name of the GrovePi plugin (which can be used in knowledge bases) is grovepi.

If using this plugin in an embedded Sponge, you have to manually install the Java 8 GrovePi library in you local Maven repository because it isn’t available in the Central Maven Repository.

For more information see the GrovePiPlugin Javadoc.

The following example shows how to turn on/off a LED connected to the GrovePi board that in turn is connected to the Raspberry Pi.

Example Python knowledge base - Blinking LED
# GrovePi board: Connect LED to D4

state = False

class LedBlink(Trigger):
    def onConfigure(self):
        self.event = "blink"
    def onRun(self, event):
        global led, state
        state = not state
        led.set(state)

def onStartup():
    global led
    led = grovepi.device.getDigitalOut(4)
    sponge.event("blink").sendAfter(0, 1000)
Example XML configuration
<sponge>
    <knowledgeBases>
        <knowledgeBase name="kb">
            <file>led_blink.py</file>
        </knowledgeBase>
    </knowledgeBases>
    <plugins>
        <plugin name="grovepi" class="org.openksavi.sponge.rpi.grovepi.GrovePiPlugin" />
    </plugins>
</sponge>
3.14.12.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-rpi-grovepi</artifactId>
    <version>1.6.0</version>
</dependency>

3.14.13. TensorFlow

Sponge provides integration with TensorFlow. TensorFlow could be used for machine learning applications such as neural networks. The machine learning is a subset of Artificial Intelligence.

Although there could be many ways of using TensorFlow from Java, this integration uses the Py4J library wrapped in the Py4J plugin to communicate between a Sponge Java process and a Python program running TensorFlow. The TensorFlow Python API has been chosen over the Java API, because, at the time of writing, the TensorFlow APIs in languages other than Python were not covered by the API stability promises. For use cases that require low latency times, the usage of Py4J may be insufficient. An alternative approach is to use TensorFlow serving, designed for production environments.

3.14.13.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-tensorflow</artifactId>
    <version>1.6.0</version>
</dependency>
3.14.13.2. The Digits recognition REST API service example

This example shows how to expose the TensorFlow machine learning model trained for the MNIST database as a REST API service to recognize handwritten digits. For the complete source code see https://github.com/softelnet/sponge/tree/master/sponge-tensorflow. Please note that the Python language is used both in the Sponge knowledge base (Jython version 2.7) and in the script running TensorFlow (CPython version 3).

CPython prerequisites
# Install TensorFlow by following the guide https://www.tensorflow.org/install/, for example with Virtualenv and Python 3.
# Only the most important steps are presented hereunder.

virtualenv --system-site-packages -p python3 ~/tensorflow
cd ~/tensorflow
source ./bin/activate

# Change directory to the Sponge source code main directory and install Python dependencies.
(tensorflow)$ pip3 install -r sponge-tensorflow/examples/tensorflow/digits/requirements.txt
(tensorflow)$ deactivate
Table 39. The main components
File name Description

ImageClassifierService.java

The Java interface of the image classifier Python service. This interface is used by Py4J to expose Python functionality to a Java process.

actions/digits_predict.py

The Sponge knowledge base that contains definitions of actions that will be exposed in the Sponge REST API service. The DigitsPredict action takes a binary representation of a PNG file and passes it to the running Python script file by invoking ImageClassifierService.predict(byte[] image) method. This method will be invoked on the remote object running in the Python process.

digits_rest_server.xml

The Sponge configuration file that instructs Sponge to create the Py4J plugin, execute the Python script file that will load a TensorFlow model and start the REST API server.

digits_rest_server.py

The main Sponge knowledge base file (compatible with Jython) for that example.

python/digits_model.py

The Python script file (compatible with CPython) that defines the ConvNet model trained on the MNIST database to recognize handwritten digits. This example uses Keras neural networks API that runs on top of TensorFlow.

python/image_classifier_service.py

The Python script file (compatible with CPython) that loads the model. If the model file data/digits_model.h5 exists, it will be loaded. Otherwise a new model will be trained and saved. This model is then used by the Python-based ImageClassifierService implementation that is exposed by the Python-side Py4J gateway.

python/digits_model_create.py

The auxiliary Python script file (compatible with CPython) that manually creates, trains and saves the model. It overrides the model file. Additionally the script plots the training and validation loss side by side, as well as the training and validation accuracy.

The Sponge REST API configuration used for this example is not secure. In the production environment you should use HTTPS as well as user authentication.

3.14.14. GSM modem

Sponge provides access to a GSM modem device. The GammuGsmModemPlugin uses Gammu. The requirement is that Gammu utility has to be installed. The current implementation of the GammuGsmModemPlugin is limited. It only sends SMSes. However you may invoke gammu in a knowledge base using the Sponge ProcessInstance API.

The default name of the plugin (which can be used in knowledge bases) is gsm.

Example of sending SMS using the GammuGsmModemPlugin
class SendSms(Action):
    def onConfigure(self):
        self.displayName = "Send SMS"
        self.description = "Sends a new SMS."
        self.argsMeta = [
            ArgMeta("recipient", StringType()).displayName("Recipient").description("The SMS recipient."),
            ArgMeta("message", StringType()).displayName("Message").description("The SMS message.")
        ]
        self.resultMeta = ResultMeta(VoidType())
    def onCall(self, recipient, message):
        gsm.sendSms(recipient, message)
Example XML configuration
<plugin name="gsm" class="org.openksavi.sponge.gsmmodem.GammuGsmModemPlugin" />
Example of sending SMS using the ProcessInstance API
from org.openksavi.sponge.util.process import ProcessConfiguration, InputRedirect, OutputRedirect

def sendSms(recipient, message):
    process = sponge.runProcess(ProcessConfiguration.builder("gammu").arguments("sendsms", "TEXT", recipient,
            None if gsm.canEncodeGsm(message) else "-unicode")
            .inputRedirect(InputRedirect.STRING).inputString(message).outputRedirect(OutputRedirect.STRING).build())
    if process.waitFor() != 0:
        raise Exception("Exit code {}: {}".format(process.exitCode, process.outputString))
3.14.14.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-gsm-modem</artifactId>
    <version>1.6.0</version>
</dependency>

3.15. Best practices

When developing an application using Sponge you have to be aware of the fact that knowledge bases could be created in two categories of programming languages:

  • Java,

  • supported scripting languages (e.g. Python, Ruby, Groovy, JavaScript).

Each of these two categories has its pros and cons which makes it better for a certain use. For example scripting languages work well when flexible modification of source code is required.

Libraries written in Java or supported scripting languages may be used, however make sure that they are compatible with the implementations of these languages.

The following chapters describe the best practices for typical use cases.

3.15.1. Events

Sponge is used for developing applications based on event processing. That is why you should start with defining event types. Events should contain enough information (in the form of attributes) so that event processors could provide demanded logic. Moreover, if necessary, you should consider using event chaining, i.e. sending events of a more abstract level based on correlations of low level events.

3.15.2. Plugins

If there is a need for creating an interface to an external system, the best way is to use existing or create a new plugin. Once written plugin could be used in other Sponge based applications.

In most cases a CamelPlugin, by providing access to all Camel components, should be sufficient when integrating with various systems.

If there is a need for creating a custom plugin, in most use cases we suggest creating it in Java rather than in a scripting language.

3.15.3. Processors

When defining a processor that is not a singleton, its class implementation should provide lightweight creating of new instances.

3.15.4. Filters

Filters are especially important when an application cooperates with an external system. If such system sends events, it is a good practice to check if an event contains all expected information and if event attribute values are valid. This type of selection could be done in filters. Filters may also prevent from idly processing events that should be ignored by the application logic at an early phase as they can have an impact on the whole performance.

3.15.5. Triggers

Triggers should be implemented in a way to support concurrent processing of many events by different threads. You should avoid class level (static) variables and restrict, if possible, to data transfered in events.

3.15.6. Rules

Rules should be used when triggers functionality is not sufficient.

3.15.7. Correlators

Correlators should be used when filters, triggers and rules functionality is not sufficient for the problem you try to solve.

3.15.8. Actions

Actions should be created only when there is a need to provide some functionality that is to be used in many knowledge bases that are written in different scripting languages and only when you don’t want to write it in Java.

3.16. Scripting languages

3.16.1. Supported scripting languages

Table 40. Supported knowledge base scripting languages
Language Implementation

Python

Jython

Ruby

JRuby

Groovy

Groovy

JavaScript

Nashorn

3.16.2. Python

3.16.2.1. Limitations

Note that Jython 2.7.1 has a few bugs that could in some cases impact the stability of the system, e.g.: http://bugs.jython.org/issue2487.

3.16.2.2. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-jython</artifactId>
    <version>1.6.0</version>
</dependency>

The dependency for Jython used in Sponge is Jython shaded.

3.16.3. Ruby

3.16.3.1. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-jruby</artifactId>
    <version>1.6.0</version>
</dependency>

3.16.4. Groovy

3.16.4.1. Limitations

In Groovy you cannot define a class or a function twice in the same file. If you want to prepare a processor to reload, you have to put it in a separate file and use sponge.reloadClass() method. That separate file could be modified and reloaded.

Example
void onLoad() {
    sponge.reloadClass(TriggerA)
    sponge.enable(TriggerA)

    sponge.reloadClass(ActionA)
    sponge.enable(ActionA)
    sponge.call("ActionA")
}

For every knowledge base file there is a new Groovy Script instance created. For example when reloading, a new Groovy Script instance is created for each knowledge base file and they are placed in a list (in a reverse order) to be used by the Sponge Groovy interpreter internally.

It seems that a Groovy-based knowledge base must have at east one function (may be empty). Otherwise you may get org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack exception.

3.16.4.2. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-groovy</artifactId>
    <version>1.6.0</version>
</dependency>

3.16.5. JavaScript

JavaScript interpreter supports shell scripting extensions in Nashorn to provide simpler shell integration.

3.16.5.1. Limitations
3.16.5.1.1. Custom class attributes and methods

There is a limitation for using custom class attributes and methods in processors written in JavaScript implementation Nashorn. In that case you should set a class field target in the onInit() method as in the following example. All class fields and methods that are new (i.e. not inherited from the base classes) must be defined in target.

JavaScript target
var HeartbeatFilter = Java.extend(Filter, {
    onConfigure: function(self) {
        self.event = "heartbeat";
    },
    onInit: function(self) {
        self.target = new function() { (1)
            this.heartbeatCounter = 0;
        }
    },
    onAccept: function(self, event) {
        self.target.heartbeatCounter++; (2)
        if (self.target.heartbeatCounter > 2) {
            sponge.removeEvent(hearbeatEventEntry);
            return false;
        } else {
            return true;
        }
    }
});
1 Setting target that defines an attribute heartbeatCounter.
2 Using target for accessing attribute heartbeatCounter.
3.16.5.1.2. Abstract processors

The support for abstract processors is not implemented for processors written in JavaScript.

3.16.5.1.3. Dynamic onCall callback methods in actions

Dynamic onCall callback methods are not supported. Every JavaScript action has to implement the abstract Object onCall(Object self, Object[] args) method. Arguments are passed to an action only as an array.

JavaScript onCall method
var EmphasizeAction = Java.extend(Action, {
    onCall: function(self, args) {
        self.logger.debug("Action {} called", self.name);
        if (args.length > 0) {
            return "*** " + args[0] + " ***";
        } else {
            return args;
        }
    }
});
3.16.5.2. Maven configuration

Maven users will need to add the following dependency to their pom.xml:

<dependency>
    <groupId>org.openksavi.sponge</groupId>
    <artifactId>sponge-nashorn</artifactId>
    <version>1.6.0</version>
</dependency>

3.17. Logging

Sponge uses SLF4J facade for logging.

Examples and Sponge standalone command-line application use Logback as a logging implementation.

Java-based processors and plugins may use:

  • Sponge logging, by using the getLogger() method, e.g. getLogger().info("logging"), or

  • own loggers defined in their classes, according to the standard conventions, e.g.

    private static final Logger logger = LoggerFactory.getLogger(ConnectionPlugin.class);
    ...
        logger.info("logging");
    }
You may see ignored events (i.e. events that go to the Output Event Queue) in the logs by setting the sponge.event.ignored logger to INFO.

3.18. Examples

3.18.1. Complete example projects of embedding Sponge

The complete projects could be used as a point of reference to embed Sponge in your Java application. They are placed in sponge-examples-projects.

3.18.1.1. News project

This project shows how to process news as events. It is placed in sponge-examples-project-news (see sources).

Event flow:

  • News are manually generated and sent as Sponge events named news in onStartup function of the knowledge base named newsGenerator. Each event has custom attributes: source and title.

  • Every event named news is filtered to discard news that have empty or short (according to newsFilterWordThreshold configuration property) titles. This is done by NewsFilter filter.

  • Events named news are logged by LogNewsTrigger trigger.

  • When there are no new news events (that passed filters) for a specified time, then alarm event is sent. This is done by NoNewNewsAlarmRule rule.

  • LatestNewsCorrelator correlator listens to news events and stores the latest news in storagePlugin plugin in a Python deque. The number of latest news is configured as latestNewsMaxSize property.

  • When alarm event happens, this fact is logged by AlarmTrigger trigger using echoPlugin plugin and EmphasizeAction action.

Main class - NewsExampleMain
package org.openksavi.sponge.examples.project.news;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.openksavi.sponge.core.engine.DefaultSpongeEngine;
import org.openksavi.sponge.engine.SpongeEngine;

/**
 * Example class containing main method.
 */
public class NewsExampleMain {

    private static final Logger logger = LoggerFactory.getLogger(NewsExampleMain.class);

    /** XML configuration file. */
    public static final String CONFIG_FILE = "config/config.xml";

    /** The engine. */
    private SpongeEngine engine;

    /**
     * Starts up an engine.
     */
    public void startup() {
        if (engine != null) {
            return;
        }

        // Use EngineBuilder API to create an engine.
        engine = DefaultSpongeEngine.builder().config(CONFIG_FILE).build();

        // Start the engine.
        engine.startup();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                shutdown();
            } catch (Throwable e) {
                logger.error("Shutdown hook error", e);
            }
        }));
    }

    /**
     * Shutdown the engine.
     */
    public void shutdown() {
        if (engine != null) {
            engine.shutdown();
            engine = null;
        }
    }

    public SpongeEngine getEngine() {
        return engine;
    }

    /**
     * Main method. Arguments are ignored.
     */
    public static void main(String... args) {
        new NewsExampleMain().startup();
    }
}
Sponge XML configuration file - config.xml
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <properties>
        <!-- News that have less words in the title than specified by this parameter will be rejected by filters. -->
        <property name="newsFilterWordThreshold" variable="true">3</property>
        <!-- Max size of a buffer that stores latest news. -->
        <property name="latestNewsMaxSize" variable="true">5</property>
    </properties>

    <knowledgeBases>
        <!-- Main knowledge base (implemented in Python) that uses 3 files. These files will be loaded by the same interpreter. -->
        <knowledgeBase name="main">
            <!-- Plugin implemented in Python. -->
            <file>kb/main_plugins.py</file>
            <!-- Main event processors. For the sake of clarity registration of event processors is placed in the next file. -->
            <file>kb/main_event_processors.py</file>
            <!-- Knowledge base callback functions: onInit, onLoad, etc. -->
            <file>kb/main_functions.py</file>
        </knowledgeBase>
        <!-- Actions knowledge base (implemented in JavaScript). -->
        <knowledgeBase name="actions">
            <file>kb/actions.js</file>
        </knowledgeBase>
        <!-- News generator knowledge base. -->
        <knowledgeBase name="newsGenerator">
            <file>kb/news_generator.py</file>
        </knowledgeBase>
    </knowledgeBases>

    <plugins>
        <!-- Plugin defined in Java. -->
        <plugin name="echoPlugin" class="org.openksavi.sponge.examples.project.news.MultiEchoPlugin">
            <configuration>
                <count>2</count>
            </configuration>
        </plugin>

        <!-- Plugin defined in Python. Stores the last news entry". -->
        <plugin name="storagePlugin" class="StoragePlugin" knowledgeBaseName="main">
            <configuration>
                <storedValue>no news yet</storedValue>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Python-based knowledge base 'main' file - main_event_processors.py
from java.util.concurrent.atomic import AtomicBoolean
import re, collections

# Reject news with empty or short titles.
class NewsFilter(Filter):
    def onConfigure(self):
        self.event = "news"
    def onAccept(self, event):
        title = event.get("title")
        words = len(re.findall("\w+", title))
        return title is not None and words >= int(sponge.getVariable("newsFilterWordThreshold"))

# Log every news.
class LogNewsTrigger(Trigger):
    def onConfigure(self):
        self.event = "news"
    def onRun(self, event):
        self.logger.info("News from " + event.get("source") + " - " + event.get("title"))

# Send 'alarm' event when news stop arriving for 3 seconds.
class NoNewNewsAlarmRule(Rule):
    def onConfigure(self):
        self.events = ["news n1", "news n2 :none"]
        self.duration = Duration.ofSeconds(3)
    def onRun(self, event):
        sponge.event("alarm").set("severity", 1).set("message", "No new news for " + str(self.duration.seconds) + "s.").send()

# Handles 'alarm' events.
class AlarmTrigger(Trigger):
    def onConfigure(self):
        self.event = "alarm"
    def onRun(self, event):
        self.logger.info("Sound the alarm! {}", event.get("message"))
        self.logger.info("Last news was (repeat {} time(s)):\n{}", echoPlugin.count,
                         sponge.call("EmphasizeAction", [echoPlugin.echo(storagePlugin.storedValue[-1])]))
        sponge.getVariable("alarmSounded").set(True)

# Start only one instance of this correlator for the system. Note that in this example data is stored in a plugin,
# not in this correlator.
class LatestNewsCorrelator(Correlator):
    def onConfigure(self):
        self.events = ["news"]
        self.maxInstances = 1
    def onInit(self):
        storagePlugin.storedValue = collections.deque(maxlen=int(sponge.getVariable("latestNewsMaxSize", 2)))
    def onEvent(self, event):
        storagePlugin.storedValue.append(event.get("title"))
        self.logger.debug("{} - latest news: {}", self.hashCode(), str(storagePlugin.storedValue))
Python-based knowledge base 'main' file - main_functions.py
from java.util.concurrent.atomic import AtomicBoolean

# Set initial values for variables.
def onInit():
    sponge.setVariable("alarmSounded", AtomicBoolean(False))
Python-based knowledge base 'main' file - main_plugins.py
# Plugin written in Python. Stores any value.
class StoragePlugin(Plugin):
    def onConfigure(self, configuration):
        self.storedValue = configuration.getString("storedValue", "default")
    def onInit(self):
        self.logger.debug("Initializing {}", self.name)
    def onStartup(self):
        self.logger.debug("Starting up {}", self.name)
    def getStoredValue(self):
        return self.storedValue
    def setStoredValue(self, value):
        self.storedValue = value
JavaScript-based knowledge base 'actions' file - actions.js
/**
 * Sponge Knowledge base
 */

var EmphasizeAction = Java.extend(Action, {
    onCall: function(self, args) {
        self.logger.debug("Action {} called", self.name);
        if (args != null && args.length > 0) {
            return "*** " + args[0] + " ***";
        } else {
            return null;
        }
    }
});
Python-based knowledge base that generates sample news - news_generator.py
# Utility function.
def sendNewsEvent(source, title, delay):
    sponge.event("news").set("source", source).set("title", title).sendAfter(delay)

# Send sample events carrying news on startup.
def onStartup():
    allNews = ["First people landed on Mars!", "Ups", "Martians are happy to meet their neighbors"]
    for i in range(len(allNews)):
        sendNewsEvent("newsSourceA", allNews[i], i * 1000)
Java-based plugin class - MultiEchoPlugin
package org.openksavi.sponge.examples.project.news;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.openksavi.sponge.config.Configuration;
import org.openksavi.sponge.java.JPlugin;

/**
 * Java-based plugin.
 */
public class MultiEchoPlugin extends JPlugin {

    private static final Logger logger = LoggerFactory.getLogger(MultiEchoPlugin.class);

    private int count = 1;

    public MultiEchoPlugin() {
        //
    }

    public MultiEchoPlugin(String name) {
        super(name);
    }

    @Override
    public void onConfigure(Configuration configuration) {
        count = configuration.getInteger("count", count);
    }

    @Override
    public void onInit() {
        logger.debug("Initializing {}", getName());
    }

    @Override
    public void onStartup() {
        logger.debug("Starting up {}", getName());
    }

    public String echo(String text) {
        return StringUtils.repeat(text, ", repeat: ", count).toUpperCase();
    }

    public int getCount() {
        return count;
    }
}
3.18.1.2. Camel RSS News project

This example is an enhancement over the News project example. It is placed in sponge-examples-project-camel-rss-news (see sources).

The main change here is that news are acquired as RSS feeds from news services: BBC and CNN. Reading RSS feeds and transformation to Sponge events is performed in a Camel route. Sponge acts as a producer in this Camel route. This example shows Sponge as a consumer in other Camel routes as well.

This example also presents integration with Spring framework. A service provided as a Spring bean is accessed from the script knowledge base.

Knowledge bases main and actions that existed in the News project example are not changed. This is because the main processing is independent of the input and output interfaces, protocols or data structures. Internal events (in this case news events) are normalized.

Event flow:

  • RSS feeds are read from external sources, transformed to Sponge events and sent to the Sponge engine. This is done in Camel routes.

  • The main knowledge base related event flow is the same as in the previous example.

  • After the time configured as a property durationOfReadingRss Camel routes that read RSS feeds from external sources are stopped. It simulates lack of new news. This is done in the simulator knowledge base.

  • When alarm event happens, not only AlarmTrigger (as described in the previous example) handles that event, but here also ForwardAlarmTrigger trigger, defined in the consumer knowledge base. It sends an alarm message to:

    • all Camel endpoints that use the Sponge engine as a consumer in their routes,

    • to a specific endpoint given as URI.

Main class - CamelRssNewsExampleMain
package org.openksavi.sponge.examples.project.camelrssnews;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

import org.openksavi.sponge.engine.SpongeEngine;

/**
 * Example class containing main method.
 */
public class CamelRssNewsExampleMain {

    /** Spring context. */
    private GenericApplicationContext context;

    /**
     * Starts up Spring context (with the engine) manually.
     */
    public void startup() {
        if (context != null) {
            return;
        }

        // Starting Spring context.
        context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        context.registerShutdownHook();
        context.start();
    }

    public SpongeEngine getEngine() {
        return context.getBean(SpongeEngine.class);
    }

    /**
     * Shutdown Spring context.
     */
    public void shutdown() {
        if (context != null) {
            context.stop();
            context.close();
            context = null;
        }
    }

    /**
     * Main method. Arguments are ignored.
     */
    public static void main(String... args) {
        new CamelRssNewsExampleMain().startup();
    }
}
Spring Java configuration - SpringConfiguration
package org.openksavi.sponge.examples.project.camelrssnews;

import java.util.Map;

import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import org.openksavi.sponge.EngineOperations;
import org.openksavi.sponge.camel.CamelUtils;
import org.openksavi.sponge.camel.SpongeCamelConfiguration;
import org.openksavi.sponge.engine.SpongeEngine;
import org.openksavi.sponge.spring.SpringSpongeEngine;

/**
 * Spring configuration that creates the engine and Camel context.
 */
@Configuration
@ComponentScan
public class SpringConfiguration extends SpongeCamelConfiguration {

    /** RSS source header name. */
    public static final String HEADER_SOURCE = "source";

    /**
     * The engine is started by Spring at once, in order to load configuration variables (e.g. rssSources) before creating Camel routes.
     *
     * @return the engine.
     */
    @Bean
    public SpongeEngine camelRssEngine() {
        // Use EngineBuilder API to create an engine with the configuration file. Also bind Spring and Camel plugins as beans manually.
        return SpringSpongeEngine.builder().config(CamelRssConstants.CONFIG_FILE).plugins(springPlugin(), camelPlugin()).build();
    }

    /**
     * Camel routes for reading RSS feeds. Routes could be also defined in XML, Groovy or scripting knowledge bases.
     *
     * @return route builder.
     */
    @Bean
    public RouteBuilder rssInputRoute() {
        return new RouteBuilder() {

            // @formatter:off
            @SuppressWarnings("unchecked")
            @Override
            public void configure() throws Exception {
                EngineOperations operations = camelRssEngine().getOperations();
                Map<String, String> rssSources = operations.getVariable(Map.class, CamelRssConstants.VAR_RSS_SOURCES);

                // Read RSS feeds from all configured sources.
                rssSources.forEach((source, url) ->
                        from("rss:" + url + operations.getVariable(CamelRssConstants.VAR_RSS_ENDPOINT_PARAMETERS, "")).routeId(source)
                            .setHeader(HEADER_SOURCE).constant(source)
                            .to("direct:rss"));

                // Gathers RSS from different sources and sends to Sponge engine as a normalized event.
                from("direct:rss").routeId("rss")
                        .marshal().rss()
                        // Deduplicate by title.
                        .idempotentConsumer(xpath("/rss/channel/item/title/text()"),
                                MemoryIdempotentRepository.memoryIdempotentRepository())
                        // Conversion from RSS XML to Sponge event with attributes.
                        .process((exchange) -> exchange.getIn().setBody(operations.event("news")
                                .set("source", exchange.getIn().getHeader(HEADER_SOURCE))
                                .set("channel", CamelUtils.xpath(exchange, "/rss/channel/title/text()"))
                                .set("title", CamelUtils.xpath(exchange, "/rss/channel/item/title/text()"))
                                .set("link", CamelUtils.xpath(exchange, "/rss/channel/item/link/text()"))
                                .set("description", CamelUtils.xpath(exchange, "/rss/channel/item/description/text()"))
                                .make()))
                        //.filter((exchange) -> false)
                        .to("sponge:camelRssEngine");
            }
            // @formatter:on
        };
    }

    /**
     * Camel routes that use the engine as a consumer (directly or indirectly).
     *
     * @return route builder.
     */
    @Bean
    public RouteBuilder consumerRoute() {
        return new RouteBuilder() {

            @Override
            public void configure() throws Exception {
                // @formatter:off
                from("sponge:camelRssEngine").routeId("spongeConsumer")
                        .log("Received Camel message: ${body}");

                from("direct:log").routeId("directLog")
                        .log("${body}");
                // @formatter:on
            }
        };
    }

    /**
     * Camel producer template used by Sponge Camel component.
     *
     * @return producer template.
     * @throws Exception Camel context specific exception.
     */
    @Bean
    public ProducerTemplate spongeProducerTemplate() throws Exception {
        return camelContext().createProducerTemplate();
    }
}
Sponge XML configuration file - config.xml
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <properties>
        <!-- News that have less words in the title than specified by this parameter will be rejected by filters. -->
        <property name="newsFilterWordThreshold" variable="true">3</property>
        <!-- Max size of a buffer that stores latest news. -->
        <property name="latestNewsMaxSize" variable="true">5</property>
        <!-- RSS endpoint URI parameters. -->
        <property name="rssEndpointParameters" variable="true">?sortEntries=false&amp;consumer.delay=1000</property>
        <!-- Duration of reading RSS feeds from sources (in seconds). -->
        <property name="durationOfReadingRss" variable="true">5</property>
    </properties>

    <knowledgeBases>
        <knowledgeBase name="config">
            <!-- Extended configuration (more complex data structures than in properties section). -->
            <file>kb/config.py</file>
        </knowledgeBase>
        <!-- Main knowledge base (implemented in Python) that uses 3 files. These files will be loaded by the same interpreter. -->
        <knowledgeBase name="main">
            <!-- Plugin implemented in Python. -->
            <file>kb/main_plugins.py</file>
            <!-- Main event processors. For the sake of clarity registration of event processors is placed in the next file. -->
            <file>kb/main_event_processors.py</file>
            <!-- Knowledge base callback functions: onInit, onLoad, onStartup, etc. -->
            <file>kb/main_functions.py</file>
        </knowledgeBase>
        <!-- Actions knowledge base (implemented in JavaScript). -->
        <knowledgeBase name="actions">
            <file>kb/actions.js</file>
        </knowledgeBase>
        <!-- A knowledge base that simulates lack of new news after a specified time by stopping corresponding Camel routes. -->
        <knowledgeBase name="simulator">
            <file>kb/simulator.py</file>
        </knowledgeBase>
        <!-- As a consumer in Camel routes. -->
        <knowledgeBase name="consumer">
            <file>kb/consumer.py</file>
        </knowledgeBase>
    </knowledgeBases>

    <plugins>
        <!-- Plugin defined in Java. -->
        <plugin name="echoPlugin" class="org.openksavi.sponge.examples.project.camelrssnews.MultiEchoPlugin">
            <configuration>
                <count>2</count>
            </configuration>
        </plugin>

        <!-- Plugin defined in Python. Stores the last news entry. -->
        <plugin name="storagePlugin" class="StoragePlugin" knowledgeBaseName="main">
            <configuration>
                <storedValue>no news yet</storedValue>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Python-based extended configuration - config.py
# Set configuration variables.
# For the sake of clarity setting of configuration variables is done in the main level of the script. This code typically would be
# in onInit() callback function. However, because these are constants, a potential reload (causing this code to be executed once more)
# wouldn't cause any problems.
sponge.setVariable("rssSources", {"BBC":"http://rss.cnn.com/rss/edition.rss", "CNN":"http://feeds.bbci.co.uk/news/world/rss.xml"})
Python-based knowledge base that sends messages to Camel as a consumer - consumer.py
# Sends alarm messages to Camel endpoints in two ways.
class ForwardAlarmTrigger(Trigger):
    def onConfigure(self):
        self.event = "alarm"
    def onRun(self, event):
        # Emit the alarm message to all Camel endpoints that use the engine as a consumer.
        camel.emit(event.get("message"))

        # Send the alarm message to a specific endpoint.
        camel.sendBody("direct:log", event.get("message"))

        sponge.getVariable("alarmForwarded").set(True)
3.18.1.3. REST API Demo Service

The Demo Service use case shows how to deploy the REST API as a servlet. It uses the simple security strategy.

Running

First, you have to create the web application and Sponge scripts.

cd sponge-examples-projects/sponge-examples-project-demo-service
mvn clean install -Pall

The resulting archive target/sponge-demo-api.war is the web application providing the Demo REST API service. The archive target/sponge-scripts.zip contains Sponge script files and the Digits recognition example files (see the TensorFlow integration chapter) that will be accessed by the web application.

Assuming that Tomcat is installed in /opt/tomcat and the Sponge script files and the Digits recognition example files are extracted into the /opt/tomcat/sponge directory, you should add the following properties to the catalina.properties file:

sponge.home=/opt/tomcat/sponge
digits.home=/opt/tomcat/sponge/digits
password.file=/opt/tomcat/sponge/password.txt

The sample file password.txt contains the hashed, insecure password password for the user admin. The user admin has access to more actions that the anonymous user. This simple password can be used only for development and tests. In the case of publishing the service, this file should contain the hashed, secret and strong password.

# Create the password file.
sudo echo -n <username><password> | shasum -a 256 | awk '{ print $1 }' > /opt/tomcat/sponge/password.txt

# Setup privileges.
cd /opt/tomcat
sudo chown -R tomcat:tomcat sponge

# Restart Tomcat.
sudo systemctl restart tomcat.service

Deploy the web application as sponge-demo-api using the Tomcat Web Application Manager. Then test the service.

curl -i -k -X POST -H "Content-type:application/json" http://localhost:8080/sponge-demo-api/sponge.json/v1/version

You may also run this example using the Jetty server started by the maven command:

Example of the REST API servlet
cd sponge-examples-projects/sponge-examples-project-demo-service
mvn jetty:run
3.18.1.4. IoT on Raspberry Pi

The IoT on Raspberry Pi project shows how to use Sponge to read sensors, set actuators, take pictures, send SMS messages, send emails and execute OS commands.

The Sponge standalone command line application is installed on a Raspberry Pi with a GrovePi extension board. Sponge provides a synchronous REST API to remotely call actions (that for example change state of actuators). It also sends sensor data (temperature, humidity and light) to an MQTT broker using Apache Camel. The project allows processing sensor data on two levels: locally on the Raspberry Pi edge device by Sponge (to avoid sending too much data to a management system) or by an external system that connects to the MQTT broker.

3.18.1.4.1. The hardware
Table 41. The sensors and actuators connected to the GrovePi
Sensor / actuator Description

DHT sensor

Connected to the port D2.

Light sensor

Connected to the port A1.

Rotary angle sensor

Connected to the port A0.

Sound sensor

Connected to the port A2.

Red LED

Connected to the port D4.

Blue LED

Connected to the port D5.

Buzzer

Connected to the port D7.

LCD RGB Backlight

Connected to the port I2C-1.

Table 42. Other hardware connected to the Raspberry Pi
Name Description

HD Night Vision IR camera

Huawei E3131h-2 modem

Connected via a powered USB hub.

3.18.1.4.2. Prerequisites

The Linux distribution used for this example is Raspbian. All command are invoked by the user pi. For SMS sending the gammu utility should be installed.

$ sudo apt-get install gammu
3.18.1.4.3. Installation

First you should download and unpack the Sponge standalone command line application into the /home/pi/local/app/ directory. The directory /home/pi/local/app/examples/sponge-iot-rpi (containing the example knowledge base files) should be copied to /home/pi/local/ in order to modify the configuration files in a fresh copy.

The preferred installation is as a systemd service.

$ sudo vim /lib/systemd/system/sponge_iot.service
[Unit]
Description=Sponge IoT Service
After=multi-user.target

[Service]
Type=simple
ExecStart=/bin/bash /home/pi/local/app/sponge-1.6.0/bin/sponge -c /home/pi/local/sponge-iot-rpi/kb/sponge_iot.xml -Dsponge.home=/home/pi/local/sponge-iot-rpi
WorkingDirectory=/home/pi/local/sponge-iot-rpi/
KillSignal=SIGINT

[Install]
WantedBy=multi-user.target
$ sudo chmod 644 /lib/systemd/system/sponge_iot.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable sponge_iot.service
3.18.1.4.4. Configuration

The sponge_iot.properties file allows the configuration of the service name, the phone number that will receive SMS notifications, the email address for notifications, the temperature threshold to trigger sending an SMS notification, the email client settings and the MQTT broker settings.

Note that the provided password file password.txt stores sample passwords. For each user the password is: password.

3.18.1.4.5. REST API Actions

The subset of Sponge actions is published via the Sponge REST API. The published actions have their metadata configured. These actions could be used by the Sponge mobile client application to manage the IoT device using a GUI.

Table 43. The published actions
Name Description

SetGrovePiMode

Sets the GrovePi mode (auto or manual). In the auto mode the device behavior is automated according to the following rules. The LCD display shows the current temperature and humidity. The red LED is turned on if there is dark in the room. The blue LED light depends on the position of the rotary angle sensor. The auto mode is implemented by triggers and correlators. In the manual mode the actuators (LCD, LEDs, etc.) can be managed manually via the published actions.

ManageLcd

Provides management of the LCD properties, i.e. the display text and color.

ManageSensorActuatorValues

Provides management of the sensor and actuator values. Reads the temperature and humidity sensor, the light sensor, the rotary angle sensor and the sound sensor. Sets the values of the LEDs and the buzzer.

TakePicture

Takes a picture using the RPI camera.

SendNotificationEmail

Sends a notification email to the configured recipient.

SendNotificationSms

Sends a notification SMS to the configured recipient.

OsGetDiskSpaceInfo

Executes df -h and returns the disk space info as a markdown string.

OsDmesg

Executes dmesg and returns the output as a markdown string.

The REST API uses the simple security strategy.

3.18.1.4.6. MQTT

Sponge publishes the values of temperature, humidity and light sensors to the MQTT topics sponge/temperature, sponge/humidity and sponge/light. The topic prefix can be changed in the configuration.

3.18.1.4.7. Modifications in the knowledge bases

After installation, configuration and an initial run you could add your modifications to the knowledge bases. The preferred way to do this is:

  • Temporarily stop and disable the Sponge system service.

  • Run Sponge in an interactive mode in the current console.

$ cd ~/local/sponge-iot-rpi
$ sudo ~/local/app/sponge-1.6.0/bin/sponge -c ~/local/sponge-iot-rpi/kb/sponge_iot.xml -Dsponge.home=. -i iot
  • Open a new shell console to view logs.

$ tail -f ~/local/sponge-iot-rpi/logs/sponge-<current_date>.log
  • Open a new shell console to modify and save the knowledge base files.

  • After saving the knowledge base files, reload the knowledge bases in the interactive mode.

 > sponge.reload()
  • If the changes require restarting Sponge, exit the interactive mode (it stops the Sponge engine) and start Sponge again.

 > exit
 $ sudo ~/local/app/sponge-1.6.0/bin/sponge -c ~/local/sponge-iot-rpi/kb/sponge_iot.xml -Dsponge.home=. -i iot
  • If you modify actions and use the Sponge mobile client application to test the knowledge bases, please remember to refresh the action metadata in the GUI.

  • Repeat these steps until your knowledge bases are finished.

  • Start and enable the Sponge system service.

3.18.2. Scripting examples

The scripting examples show how to use certain Sponge functionalities in script knowledge bases. See the sources in Python examples, Ruby examples, Groovy examples and JavaScript examples.

Each of these examples is also used in the corresponding JUnit class as a test case with assertions. Note that not all of these examples will work in the standalone application because some of them require additional setup.

Table 44. Scripting examples
Name Description

actions (py, rb, groovy, js)

Shows how to use actions.

correlators (py, rb, groovy, js)

Shows how to use correlators. The correlator creates an event log - a list of events that it listens to.

correlators_duration (py, rb, groovy, js)

Shows how to use correlators with duration.

events_clone_policy (py, rb, groovy, js, XML configuration)

Shows event clone policies.

events_cron (py, rb, groovy, js)

Shows sending events using Cron.

events_removing (py, rb, groovy, js)

Shows how to remove scheduled events.

filters_deduplication (py, rb, groovy, js)

Shows how to use a deduplication filter to prevent from processing many events that carry the same information.

filters_java (py, rb, groovy, js)

Shows how to use a Java-based filter.

filters (py, rb, groovy, js)

Shows how to use script-based filters.

actions_hello_world (py, rb, <groovy, js, XML configuration)

Hello world action complete example.

triggers_hello_world (py, rb, <groovy, js, XML configuration)

Hello world trigger complete example.

knowledge_base_callbacks (py, rb, groovy, js)

Shows how to use knowledge base callback functions.

knowledge_base_load (py, rb, groovy, js)

Shows how to load an additional knowledge base file.

knowledge_base_manager (py, rb, groovy, js)

Shows knowledge base operations.

library (py, rb, groovy, js, XML configuration)

Shows how to use a scripting language specific library (e.g. httplib for Python) to check HTTPS host status.

plugins_java (py, rb, groovy, js, XML configuration)

Shows how to define and use a Java-based plugin.

plugins_kb (py, rb, groovy, js, XML configuration)

Shows how to define and use a script-based plugin.

rules (py, rb, groovy, js)

Shows how to define and use ordered rules, i.e. rules listening to ordered sequences of events. Event conditions are specified using lambda expressions as well as class methods.

rules_events (py, rb, groovy, js, XML configuration)

Shows how to define and use rules that have different event modes, durations etc.

rules_heartbeat (py, rb, groovy, js)

Heartbeat complete example.

rules_none_mode_events_conditions (py, rb, groovy, js)

Shows how to define and use rules that have none event mode and event conditions.

rules_none_mode_events (py, rb, groovy, js)

Shows how to define and use rules that have none event mode.

unordered_rules (py, rb, groovy, js)

Shows how to define and use unordered rules, i.e. rules listening to unordered sequences of events. Event conditions are specified using lambda expressions as well as class methods.

triggers (py, rb, groovy, js, XML configuration)

Shows how to define and use triggers.

triggers_event_pattern (py, rb, groovy, js)

Shows how to define and use triggers that specify events they listen to as a pattern based on a regular expression.

3.18.3. Features examples

The features examples show how to use some of Sponge features. They are not implemented in all supported scripting languages.

Table 45. Features examples
Name Description

fibonacci (py)

Shows how to send a chain of events, each carrying a Fibonacci number as an attribute.

engine_parameters (xml)

Shows how to set engine parameters in the XML configuration file.

event_pattern (py)

Shows how to use event name patterns and how to enable/disable processors manually.

spring (py, java)

Shows how to integrate with Spring framework.

camel_producer (py, java)

Shows how to handle messages coming from Apache Camel route by a Sponge trigger.

camel_consumer (py, java)

Shows how to handle messages coming from Sponge by an Apache Camel route.

camel_rss (py, java)

Shows how to integrate with Apache Camel to send and handle Sponge events based on RSS feeds. This example uses a Spring configuration.

camel_producer_overridden_action (py, java)

Shows how to handle messages coming from Apache Camel route by a Sponge trigger using an overridden Camel producer action.

camel_producer_custom_action (py, java)

Shows how to handle messages coming from Apache Camel route by a Sponge trigger using a custom Camel producer action.

camel_multiple_consumer (py, java)

Shows sending Camel messages to many endpoints in a single Sponge trigger.

py4j_java_server (cpython, xml, jython)

Shows how to integrate with CPython program using Py4J - Java server.

py4j_python_server (cpython, xml, jython, java)

Shows how to integrate with CPython program using Py4J - Python server.

py4j_java_server_tls (cpython, xml, jython)

Shows how to integrate with CPython program using Py4J - Java server with TLS security.

midi_generate_sound (py)

Shows how to generate MIDI sounds in a Sponge knowledge base.

midi_input (py)

Shows how to process MIDI messages created by an external MIDI input device.

midi_play_file (py)

Shows how MIDI messages created by a MIDI sequencer playing a MIDI file could be processed in a Sponge knowledge base.

mpd_events (py)

Shows MPD events.

mpd_set_playlist (py)

Shows how to set an MPD playlist.

3.18.4. Standalone examples

The standalone examples show how to use some of Sponge features in the standalone command-line application.

Table 46. Standalone examples
Name Description

standalone_news (sources)

This example is based on complete example project of embedding Sponge - News, but adjusted to a standalone version.

standalone_camel_rss_news (sources)

This example is based on complete example project of embedding Sponge - Camel RSS News, but adjusted to a standalone version.

camel_route_groovy (sources)

Camel routes in Groovy Spring configuration.

camel_route_xml (sources)

Camel context and routes in XML Spring configuration.

3.19. Maven artifacts

The groupId of Sponge Maven artifacts is org.openksavi.sponge.

Table 47. Sponge Maven artifacts
ArtifactId Central Maven Repository Description

sponge-parent

Yes

The parent project.

sponge-bom

Yes

The Bill Of Materials style pom.xml.

sponge-api

Yes

The Sponge API.

sponge-core

Yes

The Sponge core implementation. This artifact includes a shaded Guava library.

sponge-jython

Yes

The support for Python-based scripting knowledge bases using Jython.

sponge-jruby

Yes

The support for Ruby-based scripting knowledge bases using JRuby.

sponge-groovy

Yes

The support for Groovy-based scripting knowledge bases.

sponge-nashorn

Yes

The support for JavaScript-based scripting knowledge bases using Nashorn.

sponge-kotlin

Yes

The support for Kotlin-based non scripting knowledge bases.

sponge-signal

Yes

The wrappers for Operating System signals.

sponge-camel

Yes

The Apache Camel integration.

sponge-spring

Yes

The Spring framework integration.

sponge-py4j

Yes

The CPython integration that uses Py4J.

sponge-midi

Yes

The MIDI integration.

sponge-rpi-pi4j

Yes

The Pi4J (for Raspberry Pi) library integration.

sponge-reactivex

Yes

The ReactiveX integration.

sponge-rest-api-client

Yes

The Sponge REST API client.

sponge-rest-api-server

Yes

The Sponge REST API server.

sponge-tensorflow

Yes

The TensorFlow integration.

sponge-standalone

Yes

The standalone distribution of Sponge.

sponge-logging

Yes

The Sponge logging used by the standalone application.

sponge-test

Yes

The Sponge test support.

sponge-rpi-grovepi

No

The GrovePi (for Raspberry Pi) library integration.

sponge-examples-projects

No

Complete example projects.

sponge-distribution

No

Contains documentation, release configuration, project pages etc.

sponge-integration-tests

No

Sponge integration tests.

sponge-standalone-extensions

No

Dependencies for external libraries used by the standalone command-line application.

The following optional artifacts are not part of the standard Sponge distribution and are available under a variety of licenses (incompatible with Apache 2.0) but can be used to extend Sponge functionality.

Table 48. Sponge optional external Maven artifacts
ArtifactId License Central Maven Repository Description

sponge-mpd

GNU GPL 3.0

Yes

MPD (Music Player Daemon) integration.

3.20. Standalone command-line application

For a brief introduction to the Sponge standalone command-line application see Quickstart.

If you need additional libraries (e.g. Camel components) you should place JAR files into the lib directory. You should use only compatible versions of these libraries.

Standalone command-line application doesn’t support history of entered commands/expressions (i.e. upwards arrow doesn’t work).

3.20.1. Command-line options

Option Description

-c <arg>

Use given Sponge XML configuration file. Only one configuration file may be provided.

-k [name=]files

Use given knowledge base by setting its name (optional) and files (comma-separated). When no name is provided, a default name 'kb' will be used. This option may be used more than once to provide many knowledge bases. Each of them could use many files.

-s <file>

Use given Spring configuration file. This option may be used more than once to provide many Spring configuration files.

-m

Create an Apache Camel context.

-i [name]

Run in an interactive mode by connecting to a knowledge base interpreter. You may provide the name of one of the loaded knowledge bases, otherwise the first loaded knowledge base will be chosen.

-e

Applicable only in an interactive mode. Print all exceptions (e.g. also thrown in event processors running in other threads). Helpful for development purposes.

-h

Print help message and exit.

-v

Print the version information and exit.

-D property=value or -Dproperty=value

Set the Java system property.

3.20.2. Default parameters

Standalone command-line application sets its own default values for the following engine configuration parameters. You may change them in an XML configuration file.

Parameter Value

mainProcessingUnitThreadCount

10

asyncEventSetProcessorExecutorThreadCount

Same as mainProcessingUnitThreadCount

eventQueueCapacity

100000

Examples
# Change directory to Sponge bin/.

# Run with the specified Sponge XML configuration file.
./sponge -c ../examples/script/py/triggers_hello_world.xml

# Run with the knowledge base named 'helloWorldKb' using the specified knowledge base file.
./sponge -k helloWorldKb=../examples/script/py/triggers_hello_world.py

# Run with the knowledge base named 'kb' using the specified knowledge base file.
./sponge -k ../examples/script/py/triggers_hello_world.py

# Run with two knowledge bases.
./sponge -k filtersKb=../examples/script/py/filters.py -k heartbeatKb=../examples/script/js/rules_heartbeat.js

# Run in an interactive mode.
./sponge -k filtersKb=../examples/script/py/filters.py -i

# Run in an interactive mode and setup printing all exceptions to the console.
./sponge -k filtersKb=../examples/script/py/filters.py -i -e

# Run one knowledge base that use two files. Take caution not to use the same names for functions or classes in the files belonging to the same knowledge base.
./sponge -k ../examples/standalone/multiple_kb_files/event_processors.py,../examples/standalone/multiple_kb_files/example2.py

3.20.3. Environment variables

Optionally you may set the environment variable SPONGE_HOME.

Linux/MacOS/Unix
cd sponge-1.6.0
export SPONGE_HOME=`pwd`
Windows
cd sponge-1.6.0
set SPONGE_HOME=%cd%

3.20.4. Standalone plugin configuration parameters

Table 49. Standalone plugin configuration parameters
Name Type Description

spring

XML element

Spring configuration. A Spring context is created only when there is a spring configuration element present.

engineBeanName

String

The optional engineBeanName attribute of the spring element defines a Spring bean name that will reference the engine instance in the Spring context. The default value is spongeEngine.

camel

Boolean

The optional camel attribute of the spring element may be used to create a Camel context.

spring/file

String

Spring configuration files. The Spring context implementation used here is GenericGroovyApplicationContext, that allows to load XML and Groovy configuration files.

3.20.5. Spring

You may provide Spring configuration files using a command-line option or defining StandalonePlugin plugin in Sponge XML configuration file. This plugin allows to specify Spring configuration files that will be loaded. The name of this plugin must be "standalonePlugin".

Example of Spring configuration in StandalonePlugin
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <plugins>
        <plugin name="standalonePlugin" class="org.openksavi.sponge.standalone.StandalonePlugin">
            <configuration>
                <spring engineBeanName="someEngine">
                    <file>spring-context-example-file-1.xml</file>
                    <file>spring-context-example-file-2.xml</file>
                    <file>SpringContextExample3.groovy</file>
                </spring>
            <configuration>
        </plugin>
    </plugins>
</sponge>

This standlonePlugin sets up the Spring configuration XML file and Spring bean name that will reference the engine instance.

3.20.6. Camel

If you want to use Camel, you could setup a predefined Camel context configuration, so that a Camel context will be created automatically.

Available options are:

  • Setting <spring camel="true"> will create a Camel context using a predefined Spring Java configuration.

  • Using <spring> without setting camel attribute will not create any Camel context automatically. In that case you may setup a Camel context in a custom way (for example using Spring).

You may use only one Camel context in the Sponge standalone command-line application.

You could use Camel routes to send events to Sponge from an external systems, for example by configuring Camel Rest DSL.

3.20.6.1. Spring XML configuration
Example of Spring configuration in StandalonePlugin
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <plugins>
        <plugin name="standalonePlugin" class="org.openksavi.sponge.standalone.StandalonePlugin">
            <configuration>
                <spring camel="true">
                    <file>examples/standalone/camel_route_xml/spring-camel-xml-config-example.xml</file>
                </spring>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Camel configuration in Spring XML (spring-camel-xml-config-example.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://camel.apache.org/schema/spring
                           http://camel.apache.org/schema/spring/camel-spring.xsd">

    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <route id="spongeConsumerXmlSpringRoute">
            <from uri="sponge:spongeEngine" />
            <log message="XML/Spring route - Received message: ${body}" />
        </route>
    </camelContext>
</beans>
3.20.6.2. Spring Groovy configuration
Spring container plugin in Sponge configuration file example
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="https://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://sponge.openksavi.org https://sponge.openksavi.org/schema/config.xsd">

    <plugins>
        <plugin name="standalonePlugin" class="org.openksavi.sponge.standalone.StandalonePlugin">
            <configuration>
                <spring camel="true">
                    <file>examples/standalone/camel_route_groovy/SpringCamelGroovyConfigExample.groovy</file>
                </spring>
            </configuration>
        </plugin>
    </plugins>
</sponge>
Camel configuration in Spring Groovy (SpringCamelGroovyConfigExample.groovy)
import org.apache.camel.builder.RouteBuilder;

class GroovyRoute extends RouteBuilder {
    void configure() {
        from("sponge:spongeEngine").routeId("spongeConsumerCamelGroovySpring")
                .log("Groovy/Spring route - Received message: \${body}");
    }
}

beans {
    route(GroovyRoute)
}
3.20.6.3. Management of Camel routes in an interactive mode
Console - print camel status and routes
> print(camel.context.status)
> print(camel.context.routes)
Console - stop and remove a Camel route
> camel.context.stopRoute("rss")
> print(camel.context.removeRoute("rss"))
> print(camel.context.routes)

3.20.7. Logging and exception reporting

3.20.7.1. Non interactive mode

If you experience too many logs in the console while running a non-interactive standalone command-line application, you may want to change a logging configuration in config/logback.xml. For example to change a console threshold filter level from INFO to ERROR:

Example logging configuration
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ERROR</level>
    </filter>

To provide a custom logging configuration you may use the -D option according to the Logback documentation.

Custom logging configuration
./sponge -c ../examples/script/py/triggers_hello_world.xml -Dlogback.configurationFile=custom_logback.xml
3.20.7.2. Interactive mode

In an interactive mode a predefined console logger appender (configured in config/logback.xml) is turned off programmatically.

Exceptions thrown from other threads of the Sponge engine are not printed into the console. You may change that behavior by specifying -e command-line option.

3.20.8. REST API

You may enable the Sponge REST API in the standalone command line application but such configuration will provide no user management and a very limited security. Thus it can be used only in a secure network or for test purposes.

Manual start of the REST API (autoStart must be turned off) is required because the REST API server must start after the Camel context has started.

For more information see examples in the source code.

3.20.9. Running examples

News example
# Change directory to Sponge bin/.

# Run with the specified Sponge XML configuration file.
./sponge -c ../examples/standalone/news/config/config.xml
Camel RSS News example
# Change directory to Sponge bin/.

# Run with the specified Sponge XML configuration file.
./sponge -c ../examples/standalone/camel_rss_news/config/config.xml

3.20.10. Directory structure

Table 50. Directory structure
Directory Description

bin

Shell scripts.

config

Configuration files.

docs

Documentation.

examples

Example configurations and knowledge base files.

lib

Libraries used by Sponge.

logs

Log files.

3.20.11. Extension components

The extension components are included in the Sponge standalone command-line application distribution and could be used out of the box in Sponge knowledge bases.

3.20.11.1. Camel components and data formats

Besides Camel core components and data formats, Sponge standalone command-line application provides also a selected set of other Camel components and data formats ready to use.

Table 51. Camel components
Component Description

camel-exec

Executing system commands

camel-grape

Grape

camel-http4

HTTP

camel-mail

Mail

camel-jdbc

JDBC

camel-jms

JMS

camel-jmx

JMX

camel-mqtt

MQTT

camel-mustache

Mustache

camel-netty4

Netty

camel-netty4-http

Netty HTTP

camel-paho

Paho/MQTT

camel-quartz2

Quartz

camel-rss

RSS

camel-snmp

SNMP

camel-sql

SQL

camel-ssh

SSH

camel-stream

Input/output/error/file stream

camel-velocity

Velocity

camel-xmpp

XMPP/Jabber

Table 52. Camel data formats
Data format Description

camel-jackson

JSON

camel-csv

CSV

camel-tarfile

Tar format

camel-syslog

Syslog

3.20.11.2. Other components
Table 53. Other components

Component

Description

Commons Email

Provides an API for sending emails.

3.21. Third party software

Sponge uses or supports third party software released under various open-source licenses.

Table 54. Key third party software
Package License Description

Apache Commons Configuration

Apache 2.0

Used for reading configuration files.

Apache Commons Email

Apache 2.0

Used for sending emails.

Jython

Jython license

Supports writing scripting knowledge bases in Python.

JRuby

EPL 1.0, GPL 2 or LGPL 2.1

Supports writing scripting knowledge bases in Ruby.

Groovy

Apache 2.0

Supports writing scripting knowledge bases in Groovy.

Nashorn

GPL with a linking exception

Supports writing scripting knowledge bases in JavaScript.

Kotlin

Apache 2.0

Supports writing knowledge bases in Kotlin.

Quartz

Apache 2.0

Used for scheduling events (e.g. provides cron functionality).

Apache Camel

Apache 2.0

Used as an integration facade to external systems.

Spring framework

Apache 2.0

Used for integration with Spring framework.

Guava

Apache 2.0

Used for services and as a utilities library.

JLine

BSD

Used for handling console input in an interactive mode.

Py4J

BSD

Used for integration with CPython programs.

Pi4J

LGPL 3.0

Used for integration with Raspberry Pi hardware.

GrovePi

Apache 2.0

Used for integration with GrovePi (for Raspberry Pi) hardware.

TensorFlow

Apache 2.0

An open source machine learning framework.

RxJava

Apache 2.0

RxJava is a Java VM implementation of Reactive Extensions.

Reflections

WTFPL

Reflections is a Java runtime metadata analysis library.

JJWT

Apache 2.0

JSON Web Token for Java.

Table 55. Key third party software used by Sponge external modules
Package License Description

JavaMPD

GNU GPL Version 3

Used for integration with MPD (Music Player Daemon) server. Note that this library is GPL licensed therefore it is not fully compatible with the Apache License, Version 2.0.

The complete list of these libraries may be found in the THIRD-PARTY.txt and licenses.xml files of the standalone distribution.

4. Mobile client application

4.1. Introduction

The Sponge mobile client application is a Flutter application that provides a generic GUI to call remote Sponge actions. It can be run on both Android and iOS.

The application is currently under development. It will be released as an open source. The beta version will be released in the first quarter of 2019.

4.2. Features

The following chapters show the key features of the mobile application.

4.2.1. Connections

actions connection list
Figure 3. Selecting a connection to the Sponge instance in the action list screen

The application allows you to connect to a single Sponge REST API server at a time. You may configure many connections and choose one of them.

connections
Figure 4. List of connections to Sponge instances

You may add, edit and remove connections to Sponge instances as well as activate a connection. To remove a connection swipe the corresponding element.

connections edit
Figure 5. Editing a connection to a Sponge instance

A Sponge address is the URL of the Sponge instance.

4.2.2. Action list

actions
Figure 6. The action list

The main screen shows the list of actions defined in the connected Sponge engine. Only actions that have argument and result metadata are available. This is because the application uses a generic access to the actions utilizing their data types, display names, descriptions, features and so on. The number in the action icon is the number of action arguments.

To call an action or set action attributes tap the triangular icon on the right side of the action display name.

The floating button allows to refresh the action list from the server. The refresh button clears all entered action arguments and received results.

The application currently doesn’t supports all Sponge data types.

4.2.3. Navigation

drawer
Figure 7. The navigation drawer

The navigation drawer allows switching between the available main views.

4.2.4. Action call

action call manage lcd
Figure 8. The action call that manages the Raspberry Pi LCD display

Actions may have read only, provided arguments only to show a data from the server (see the Current LCD text attribute). The REFRESH button retrieves the current values of read only, provided arguments from the server.

The definition of the action that manages the Raspberry Pi LCD display
class ManageLcd(Action):
    def onConfigure(self):
        self.displayName = "Manage the LCD text and color"
        self.description = "Provides management of the LCD properties (display text and color). A null value doesn't change an LCD property."
        self.argsMeta = [
            ArgMeta("currentText", StringType().maxLength(256).nullable(True).features({"maxLines":2}))
                .displayName("Current LCD text").description("The currently displayed LCD text.").provided().readOnly(),
            ArgMeta("text", StringType().maxLength(256).nullable(True).features({"maxLines":2}))
                .displayName("Text to display").description("The text that will be displayed in the LCD.").provided(),
            ArgMeta("color", StringType().maxLength(6).nullable(True).features({"characteristic":"color"}))
                .displayName("LCD color").description("The LCD color.").provided().overwrite(),
            ArgMeta("clearText", BooleanType().nullable(True).defaultValue(False))
                .displayName("Clear text").description("The text the LCD will be cleared.")
        ]
        self.resultMeta = ResultMeta(VoidType())
    def onCall(self, currentText, text, color, clearText = None):
        sponge.call("SetLcd", [text, color, clearText])
    def onProvideArgs(self, names, current, provided):
        grovePiDevice = sponge.getVariable("grovePiDevice")
        if "currentText" in names:
            provided["currentText"] = ArgValue().value(grovePiDevice.getLcdText())
        if "text" in names:
            provided["text"] = ArgValue().value(grovePiDevice.getLcdText())
        if "color" in names:
            provided["color"] = ArgValue().value(grovePiDevice.getLcdColor())
action call manage sensors
Figure 9. The action call that manages the Grove Pi sensors and actuators

The action call screen allows editing the action arguments.

The definition of the action that manages the Grove Pi sensors and actuators
class ManageSensorActuatorValues(Action):
    def onConfigure(self):
        self.displayName = "Manage the sensor and actuator values"
        self.description = "Provides management of the sensor and actuator values."
        self.argsMeta = [
            ArgMeta("temperatureSensor", NumberType().nullable()).displayName(u"Temperature sensor (°C)").provided().readOnly(),
            ArgMeta("humiditySensor", NumberType().nullable()).displayName(u"Humidity sensor (%)").provided().readOnly(),
            ArgMeta("lightSensor", NumberType().nullable()).displayName(u"Light sensor").provided().readOnly(),
            ArgMeta("rotarySensor", NumberType().nullable()).displayName(u"Rotary sensor").provided().readOnly(),
            ArgMeta("soundSensor", NumberType().nullable()).displayName(u"Sound sensor").provided().readOnly(),
            ArgMeta("redLed", BooleanType()).displayName("Red LED").provided().overwrite(),
            ArgMeta("blueLed", IntegerType().minValue(0).maxValue(255)).displayName("Blue LED").provided().overwrite(),
            ArgMeta("buzzer", BooleanType()).displayName("Buzzer").provided().overwrite()
        ]
        self.resultMeta = ResultMeta(VoidType())
    def onCall(self, temperatureSensor, humiditySensor, lightSensor, rotarySensor, soundSensor, redLed, blueLed, buzzer):
        grovePiDevice = sponge.getVariable("grovePiDevice")
        grovePiDevice.setRedLed(redLed)
        grovePiDevice.setBlueLed(blueLed)
        grovePiDevice.setBuzzer(buzzer)
    def onProvideArgs(self, names, current, provided):
        values = sponge.call("GetSensorActuatorValues", [names])
        for name, value in values.iteritems():
            provided[name] = ArgValue().value(value)

class GetSensorActuatorValues(Action):
    def onCall(self, names):
        values = {}
        grovePiDevice = sponge.getVariable("grovePiDevice")
        if "temperatureSensor" or "humiditySensor" in names:
            th = grovePiDevice.getTemperatureHumiditySensor()
            if "temperatureSensor" in names:
                values["temperatureSensor"] = th.temperature if th else None
            if "humiditySensor" in names:
                values["humiditySensor"] = th.humidity if th else None
        if "lightSensor" in names:
            values["lightSensor"] = grovePiDevice.getLightSensor()
        if "rotarySensor" in names:
            values["rotarySensor"] = grovePiDevice.getRotarySensor().factor
        if "soundSensor" in names:
            values["soundSensor"] = grovePiDevice.getSoundSensor()
        if "redLed" in names:
            values["redLed"] = grovePiDevice.getRedLed()
        if "blueLed" in names:
            values["blueLed"] = grovePiDevice.getBlueLed()
        if "buzzer" in names:
            values["buzzer"] = grovePiDevice.getBuzzer()
        return values
action call send sms
Figure 10. The action call that sends an SMS from the Raspberry Pi

Actions arguments may be edited in multiline text fields.

The definition of the action that sends an SMS from the Raspberry Pi
class SendSms(Action):
    def onConfigure(self):
        self.displayName = "Send an SMS"
        self.description = "Sends a new SMS."
        self.argsMeta = [
            ArgMeta("recipient", StringType().format("phone"))
                .displayName("Recipient").description("The SMS recipient."),
            ArgMeta("message", StringType().maxLength(160).features({"maxLines":5}))
                .displayName("Message").description("The SMS message.")
        ]
        self.resultMeta = ResultMeta(VoidType())
    def onCall(self, recipient, message):
        gsm.sendSms(recipient, message)
action call color
Figure 11. The action call argument editor for a color type

The color picker widget allows a user to choose a color as an argument value.

The definition of the action that takes a color argument
class ChooseColor(Action):
    def onConfigure(self):
        self.displayName = "Choose a color"
        self.description = "Shows a color argument."
        self.argsMeta = [
            ArgMeta("color", StringType().maxLength(6).nullable(True).features({"characteristic":"color"}))
                .displayName("Color").description("The color.")
        ]
        self.resultMeta = ResultMeta(StringType())
    def onCall(self, color):
        return "The chosen color is " + color
action call digit drawing
Figure 12. The action call argument editor for a digit drawing

The drawing panel allows a user to paint an image that will be set as an argument value in an action call.

The definition of the action that recognizes a handwritten digit
class DigitsPredict(Action):
    def onConfigure(self):
        self.displayName = "Recognize a digit"
        self.description = "Recognizes a handwritten digit"
        self.argsMeta = [
            ArgMeta("image", BinaryType().mimeType("image/png")
                   .features({"characteristic":"drawing", "width":28, "height":28, "background":"000000", "color":"FFFFFF", "strokeWidth":1.5}))\
                   .displayName("Image of a digit")
        ]
        self.resultMeta = ResultMeta(IntegerType()).displayName("Recognized digit")
    def onCall(self, image):
        predictions = py4j.facade.predict(image)
        prediction = max(predictions, key=predictions.get)
        probability = predictions[prediction]

        # Handle the optional predictionThreshold Sponge variable.
        predictionThreshold = sponge.getVariable("predictionThreshold", None)
        if predictionThreshold and probability < float(predictionThreshold):
            self.logger.debug("The prediction {} probability {} is lower than the threshold {}.", prediction, probability, predictionThreshold)
            return None
        else:
            self.logger.debug("Prediction: {}, probability: {}", prediction, probability)
            return int(prediction)
action call digit
Figure 13. The action call for an attribute of type drawing

The action call screen shows all action arguments.

action call digit result
Figure 14. The action call result for a digit recognition

If the action has been called, the result is shown below the action display name. If the result can’t be fully shown in the action list, you may tap the result to see the details.

action call doodle drawing
Figure 15. The action call argument editor for a doodle drawing

Drawing panels can be configured in a corresponding action definition, where a color, a background color etc. could be specified.

The definition of the action that requires drawing a doodle
class DrawAndUploadDoodle(Action):
    def onConfigure(self):
        self.displayName = "Draw and upload a doodle"
        self.description = "Shows a canvas to draw a doodle and uploads it to the server"
        self.argsMeta = [
            ArgMeta("image", BinaryType().mimeType("image/png")
                   .features({"characteristic":"drawing", "width":300, "height":250, "background":"FFFFFF", "color":"000000", "strokeWidth":5}))\
                   .displayName("Doodle")
        ]
        self.resultMeta = ResultMeta(StringType()).displayName("Status")
    def onCall(self, image):
        fileName = str(System.currentTimeMillis()) + ".png"
        SpongeUtils.writeByteArrayToFile(image, sponge.getProperty("doodlesDir") + "/" + fileName)
        return "Uploaded as " + fileName
action call doodle
Figure 16. The action call for a doodle drawing as an argument

The action call screen shows all action arguments, for example a drawing.

action call arg depends
Figure 17. The action call that shows argument dependencies

Action arguments may depend on each other. Argument dependencies are supported in the action call panel and allow creating simple, interactive forms where some arguments are provided by the server, some entered by the user, some read only and some depend on the values of others. The important thing is that all that configuration is defined in an action in a knowledge base placed on the server side, not in the mobile application.

The definition of the action that provides arguments with dependencies
class DependingArgumentsAction(Action):
    def onConfigure(self):
        self.displayName = "Action with depending arguments"
        self.argsMeta = [
            ArgMeta("continent", StringType()).displayName("Continent").provided(),
            ArgMeta("country", StringType()).displayName("Country").provided().depends("continent"),
            ArgMeta("city", StringType()).displayName("City").provided().depends("country"),
            ArgMeta("river", StringType()).displayName("River").provided().depends("continent"),
            ArgMeta("weather", StringType()).displayName("Weather").provided(),
        ]
        self.resultMeta = ResultMeta(StringType()).displayName("Sentences")
    def onCall(self, continent, country, city, river, weather):
        return "There is a city {} in {} in {}. The river {} flows in {}. It's {}.".format(city, country, continent, river, continent, weather.lower())
    def onProvideArgs(self, names, current, provided):
        if "continent" in names:
            provided["continent"] = ArgValue().valueSet(["Africa", "Asia", "Europe"])
        if "country" in names:
            continent = current["continent"]
            if continent == "Africa":
                countries = ["Nigeria", "Ethiopia", "Egypt"]
            elif continent == "Asia":
                countries = ["China", "India", "Indonesia"]
            elif continent == "Europe":
                countries = ["Russia", "Germany", "Turkey"]
            else:
                countries = []
            provided["country"] = ArgValue().valueSet(countries)
        if "city" in names:
            country = current["country"]
            if country == "Nigeria":
                cities = ["Lagos", "Kano", "Ibadan"]
            elif country == "Ethiopia":
                cities = ["Addis Ababa", "Gondar", "Mek'ele"]
            elif country == "Egypt":
                cities = ["Cairo", "Alexandria", "Giza"]
            elif country == "China":
                cities = ["Guangzhou", "Shanghai", "Chongqing"]
            elif country == "India":
                cities = ["Mumbai", "Delhi", "Bangalore"]
            elif country == "Indonesia":
                cities = ["Jakarta", "Surabaya", "Medan"]
            elif country == "Russia":
                cities = ["Moscow", "Saint Petersburg", "Novosibirsk"]
            elif country == "Germany":
                cities = ["Berlin", "Hamburg", "Munich"]
            elif country == "Turkey":
                cities = ["Istanbul", "Ankara", "Izmir"]
            else:
                cities = []
            provided["city"] = ArgValue().valueSet(cities)
        if "river" in names:
            continent = current["continent"]
            if continent == "Africa":
                rivers = ["Nile", "Chambeshi", "Niger"]
            elif continent == "Asia":
                rivers = ["Yangtze", "Yellow River", "Mekong"]
            elif continent == "Europe":
                rivers = ["Volga", "Danube", "Dnepr"]
            else:
                rivers = []
            provided["river"] = ArgValue().valueSet(rivers)
        if "weather" in names:
            provided["weather"] = ArgValue().valueSet(["Sunny", "Cloudy", "Raining", "Snowing"])
action call arg depends value set
Figure 18. The action call that shows argument dependencies and value sets

Allowed argument values can be defined in an action and provided from the server every time the action call screen is shown or an argument dependency value changes.

4.2.5. Action result

actions binary result
Figure 19. The action binary result

Actions may return contents that can be viewed for example as a HTML or a PDF file using the mobile OS viewers.

The definitions of the actions that return a HTML and a PDF file respectively
class HtmlFileOutput(Action):
    def onConfigure(self):
        self.displayName = "HTML file output"
        self.description = "Returns the HTML file."
        self.argsMeta = []
        self.resultMeta = ResultMeta(BinaryType().mimeType("text/html")).displayName("HTML file")
    def onCall(self):
        return String("""
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
    <head>
      <title>HTML page</title>
    </head>
    <body>
        <!-- Main content -->
        <h1>Header</h1>
        <p>Some text
    </body>
</html>
""").getBytes("UTF-8")

class PdfFileOutput(Action):
    def onConfigure(self):
        self.displayName = "PDF file output"
        self.description = "Returns the PDF file."
        self.argsMeta = []
        self.resultMeta = ResultMeta(BinaryType().mimeType("application/pdf")).displayName("PDF file")
    def onCall(self):
        return sponge.process(ProcessConfiguration.builder("curl", "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
                              .outputAsBinary()).run().outputBinary
actions console result
Figure 20. The action console formatted result

Actions may return a console output, for example the result of running the df -h command on the server.

The definition of the action that returns an OS command output
class OsGetDiskSpaceInfo(Action):
    def onConfigure(self):
        self.displayName = "Get disk space info"
        self.description = "Returns the disk space info."
        self.argsMeta = []
        self.resultMeta = ResultMeta(StringType().format("console")).displayName("Disk space info")
    def onCall(self):
        return sponge.process(ProcessConfiguration.builder("df", "-h").outputAsString()).run().outputString
actions markdown result
Figure 21. The action Markdown formatted result

Actions may return a Markdown formatted text.

4.2.6. User experience

dark theme
Figure 22. The application dart theme

The application may be switched to the dark theme in the settings.

4.2.7. Included demos

The access to actions in the mobile application is generic. However the application may include demos that use a customized UI.

4.2.7.1. Handwritten digit recognition
drawer digits
Figure 23. The navigation drawer if connected to a Sponge instance that supports a digit recognition

If the current connection points to a Sponge instance that has the required action that performs a handwritten digit recognition, this demo is enabled in the navigation drawer.

digits info
Figure 24. The digit recognition demo - the information dialog
digits drawing
Figure 25. The digit recognition demo - drawing a digit

The digit recognition demo screen allows drawing a digit that will be recognized by the Sponge action. After each stroke the remote action call is made and the result is shown in the circle.

5. Release notes

A high-level view of the changes introduced in Sponge releases.

Unless noted otherwise in the release notes, Sponge releases (starting with version 1.5.0) that have the same major.minor numbers are compatible.

1.6.0 (2019-01-11)

  • A noteworthy new feature: There is a possibility to provide action argument values and possible value sets in the action configuration. It makes easier creating a generic UI for an action call that reads and presents the actual state of the entities that are to be changed by the action and its arguments.

  • Added a new REST API operation actionArgs that fetches the provided action arguments from the server.

  • API change: The action calling methods, e.g. EngineOperations.call(String actionName, Object…​ args) have been changed to EngineOperations.call(String actionName, List<Object> args) and EngineOperations.call(String actionName). So for example the previous call sponge.call("HelloWorldAction", "Sponge user") should be now sponge.call("HelloWorldAction", ["Sponge user"]).

  • API change: Renamed class Type to DataType and TypeKind to DataTypeKind.

  • API change: Renamed classes in the REST API client, including SpongeRestApiClient to SpongeRestClient and SpongeRestApiClientConfiguration to SpongeRestClientConfiguration.

  • API change: Removed SpringSpongeRestClient from the REST API client because the underlying Spring RestTemplate will be deprecated.

  • API change: Renamed methods in CamelPlugin: send(body) to emit(body), send(uri, body) to sendBody(uri, body) and request(uri, body) to requestBody(uri, body).

  • API change: Major changes in an external process handling, including ProcessInstance and ProcessConfiguration.

  • API change: Removed alternative, deprecated methods for creating and sending events in EngineOperations.

  • API change: Renamed Event.getOrDefault to Event.get.

  • API change: Removed ActionType because there is a more versatile feature of providing action argument values and value sets.

  • Added a new validation that an event name must not contain white spaces nor colons.

  • The default value of the prettyPrint option in the REST API is now false.

  • Added a context and listeners to the REST API client that provide access to JSON texts in a client code.

  • Added a critical section in event handling by a correlator.

  • Added a new property to correlators: instanceSynchronous.

  • Added a new data type AnnotatedType.

  • Documentation includes the REST API client for Dart.

  • Improved error handling in the REST API server.

  • Added the -D option in the standalone command-line application for passing Java system properties.

  • Removed some of the Camel components from the standalone command-line application to reduce the overall package size.

  • Cleanup in the Digits recognition example.

  • Libraries upgrade.

1.5.0 (2018-11-08)

  • API change: Removed the tags property from a type (used in an action argument and in an action result metadata) because the existing features property is sufficient.

  • API change: The nameRegExp property in the GetActionsRequest in the REST API has been renamed to name. The default value of the metadataRequired property is now false.

  • Added the optional property to the ArgMeta.

  • Action metadata cache in the REST API Java client.

  • Fixed usage of OkHttpClient in the REST API Java client.

  • Libraries upgrade.

1.4.2 (2018-09-14)

  • API change: ArgMeta.required in the REST API has been replaced by Type.nullable.

  • Changes in the REST API Java client, e.g. a full URL instead of hostname/port in a configuration, support a the OkHttp-based client (default) and for a Spring-based client (optional).

  • Enhancements in running external executable as a subprocess, e.g. binary redirect type, waiting for an error line from the subprocess text output.

  • The REST API server publishReload configuration parameter has now the default value false.

  • Libraries upgrade.

1.4.1 (2018-08-22)

  • API change: A processor meta property has been renamed to features.

  • Optional knowledge base versioning.

  • A type for an action argument or result metadata may specify one or more features.

  • Libraries upgrade.

1.4.0 (2018-08-01)

  • API change: the EPS global variable for the engine operations has been renamed to sponge.

  • A new REST API client artifact sponge-rest-api-client. The existing REST API server artifact has been renamed to sponge-rest-api-server.

  • Optional auth-token based authentication in the REST API.

  • A new artifact for TensorFlow integration sponge-tensorflow along with a complete example.

  • Enhancements in Py4J plugin configuration for running external Python scripts as subprocesses.

  • A type for an action argument or result metadata may specify one or more tags.

  • Libraries upgrade.

1.3.3 (2018-07-12)

  • Added a new attribute to the REST API actions operation that allows specifying an action name or a regular expression.

  • Added a new ActionType type that allows using a result of one action to be a type for another action argument or result.

  • Fixed the error that may happen while loading optional knowledge base files if a directory doesnt’t exist.

  • The sponge-core artifact now shades the Reflections artifact (that use Guava).

1.3.2 (2018-07-06)

  • Fixed the error that may happen while loading optional knowledge base files using the SpringKnowledgeBaseFileProvider.

  • Libraries upgrade (most notably Spring to version 5).

1.3.1 (2018-07-04)

  • Java-based processors may be enabled and disabled by scanning Java packages (enableJavaByScan).

  • Knowledge base files may be specified using wildcards.

  • Support for custom roles in the REST API. Roles are represented as String rather than an enumeration.

  • Support for complex data types in the REST API. A type specification in action metadata has changed.

  • Minor bug fixes.

  • Libraries upgrade.

1.3.0 (2018-06-20)

  • API change: Action.onCall callback method behavior has been changed to dynamic. Custom actions define onCall methods with the arbitrary number of named arguments, for example def onCall(self, value, text): in Python.

1.2.0 (2018-06-18)

  • API change: Action.onCall callback method signature has been changed to Object onCall(Object[] args).

  • A new Sponge REST API (sponge-rest-api) that uses JSON as its communication format.

  • Support for simple metadata (map of String to Object) for processors.

  • Support for metadata for arguments and results of actions.

  • Support for pre/post config file plugins and knowledge bases in Engine Builder.

  • Libraries upgrade.

1.1.0 (2018-04-12)

  • Support for using ReactiveX (sponge-reactivex) in knowledge bases, e.g. for processing a stream of Sponge events using reactive programming.

  • Added support for passing a knowledge base script as a String to the EngineBuilder API.

  • Fixed the error preventing closing a standalone application when there was an exception thrown in onShutdown callback function in a knowledge base.

  • Pi4J (sponge-rpi-pi4j) and GrovePi (sponge-rpi-grovepi) integration examples.

  • Libraries upgrade.

1.0.8 (2018-03-14)

  • API change: The Engine interface has been renamed to SpongeEngine. The implementations have been renamed accordingly. The EngineCamelConfiguration class has been renamed to SpongeCamelConfiguration.

  • Added a new callback function onRun to knowledge bases that allows a user to, for example, just run a script and stop the engine.

  • Simplified specification of rule event conditions in Java and Kotlin.

  • Added a simple support for Raspberry Pi: Pi4J (sponge-rpi-pi4j) and GrovePi (sponge-rpi-grovepi).

1.0.7 (2018-02-02)

  • Added MIDI support. The MIDI plugin allows processing MIDI messages by the Sponge and provides communication with MIDI devices.

  • API change: The Utils class has been renamed to SpongeUtils.

  • Added support for writing non script knowledge bases in Kotlin.

  • Added support for specifying non script knowledge bases in an XML configuration.

  • A type of a script knowledge base is no logger required in an XML configuration when knowledge base files are specified.

  • The sponge-core artifact now shades Guava, so the sponge-core-shaded artifact has been removed since it is no longer needed.

  • Downgraded Jython to version 2.7.0 to prevent stability issues.

  • Added sponge-external group of projects in order to provide functionalities that require licenses incompatible with Apache 2.0.

  • Added MPD support (in sponge-external, because it is GNU GPL 3.0 licensed).

  • Libraries upgrade.

1.0.6 (2017-12-01)

  • The new, shaded version of sponge-core named sponge-core-shaded for an embedded use in custom applications that experience version conflicts in Guava or Quartz. Now you have to explicitly add a dependency to either sponge-core or sponge-core-shaded in your pom.xml.

  • Support for integration with CPython using Py4J.

  • The new startup system event.

  • Libraries upgrade.

1.0.5 (2017-10-12)

  • API change: The EngineOperations method callAction has been renamed to call.

  • The new attribute required in the knowledge base file configuration that, if set to false, allows specifying optional (non existing) knowledge base files.

  • Script knowledge base files are looked up also in the file system relative to the XML configuration file parent directory.

  • Abstract processor classes in script knowledge bases are now compatible with the auto-enable mechanism. The support for abstract processors is implemented for Python, Groovy, Ruby but not for JavaScript.

  • CamelProducerAction may be also set in the Camel In message header named CamelSpongeAction.

  • SpringEngine may be configured not to start immediately (see SpringEngineBuilder.autoStartup method).

  • Improvements in the interactive mode of the standalone command-line application (now JLine is used, supporting for example a command history).

  • Fixed the error preventing substitution of properties defined via the EngineBuilder API in the properties section in the XML configuration files.

1.0.4 (2017-09-20)

  • Implementation of unordered rules, i.e. rules that listen to unordered sequences of events. Unordered rules is a new feature that should be treated as an experimental one.

  • API change: The Rule methods setConditions and setAllConditions have been renamed respectively to addConditions and addJavaConditions.

  • Fixed the error in the interactive mode of the standalone application preventing exit when using exit or quit command.

  • Libraries upgrade.

  • Distribution as a Docker container.

1.0.3 (2017-08-30)

  • Optimization of event set processors initialization.

  • Optimization of creating new instances of processors in script-based interpreters.

  • API change: onInit in correlators will be invoked after onAcceptAsFirst, not before.

1.0.2 (2017-08-23)

  • Event name patterns in event processors.

  • Removed matches method in Event.

  • EventDefinition send methods accept Duration and Instant types.

  • Fixed the concurrency problem in event set processors.

  • New tests for scripting support.

  • Libraries upgrade.

1.0.1 (2017-08-21)

  • Fixed the possible SyncAsyncEventSetProcessorMainProcessingUnitHandler concurrency error.

1.0.0 (2017-08-18)

The initial release.