Quickstart
1. Concepts
This chapter describes basic concepts behind Sponge. For more information see User Guide.
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.
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 action and 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).
1.3. Plugins
The connection between the outside world and knowledge bases is provided by plugins.
1.4. Event flow
The figure below shows the event flow in the Sponge engine.
2. Distributions
2.1. Docker
The standalone command-line program or a Sponge service can be run as a Docker container.
The Docker container is based on OpenJDK 11.
docker run -it --rm openksavi/sponge -i -l python
docker run -it --rm openksavi/sponge -c examples/script/py/triggers_hello_world.xml -i
docker run -it --rm --entrypoint "/bin/bash" openksavi/sponge
./sponge -h
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.
docker run -it -v ~/examples:/opt/examples openksavi/sponge -c /opt/examples/script/py/triggers_hello_world.xml
Press CTRL+C
after seeing the message "Hello World!"
to exit the Sponge loop.
2.1.1. Remote Sponge service template
The Remote Sponge service template allows you to run your own Sponge service and use it remotely in the Sponge Remote mobile app for Android.
-
First run an example of a remote Sponge service with a
HelloWorld
action.
docker run -it --rm -p 1836:1836 -p 1837:1837 openksavi/sponge -c services/remote/remote.xml -Dsponge.serviceDir=examples/standalone/remote_service
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/call -d '{"params":{"name":"HelloWorld","args":["User"]}}'
{"jsonrpc":"2.0","result":{"value":"Hello World! Hello User!"},"id":null}
Press CTRL+C
to stop the Sponge service.
-
Then run your own remote Sponge service with your
HelloWorld
action.
mkdir remote_service
cd remote_service
vi service.py
class MyHelloWorld(Action):
def onConfigure(self):
self.withLabel("My hello world").withDescription("Returns a greeting text.")
self.withArg(StringType("name").withLabel("Your name").withDescription("Type your name."))
self.withResult(StringType().withLabel("Greeting").withDescription("The greeting text."))
def onCall(self, name):
return "My Hello World! Hello {}!".format(name)
docker run -it --rm -p 1836:1836 -p 1837:1837 -v `pwd`:/opt/service openksavi/sponge -c services/remote/remote.xml -Dsponge.serviceDir=/opt/service
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/call -d '{"params":{"name":"MyHelloWorld","args":["User"]}}'
{"jsonrpc":"2.0","result":{"value":"My Hello World! Hello User!"},"id":null}
-
Finally run your own remote Sponge service with a more useful action that displays free disk space in your Docker container.
vi service.py
class GetDiskSpaceInfo(Action):
def onConfigure(self):
self.withLabel("Get disk space info").withDescription("Returns the disk space info.")
self.withNoArgs().withResult(StringType().withFormat("console").withLabel("Disk space info"))
self.withFeature("icon", "console")
def onCall(self):
return sponge.process("df", "-h").outputAsString().run().outputString
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/call -d '{"params":{"name":"EngineReload"}}'
{"jsonrpc":"2.0","result":{"value":null},"id":null}
Install the jq
command-line JSON processor if there is none.
curl -s -X POST -H "Content-type:application/json" http://localhost:1836/call -d '{"params":{"name":"GetDiskSpaceInfo"}}' | jq -r '.result.value'
Filesystem Size Used Avail Use% Mounted on
overlay 63G 47G 14G 78% /
tmpfs 64M 0 64M 0% /dev
osxfs 932G 867G 51G 95% /opt/service
/dev/vda1 63G 47G 14G 78% /etc/hosts
-
Use the Sponge Remote mobile app for Android (currently in alpha phase) to connect to the service.
Install the Sponge Remote mobile app for Android from Google Play.
After the app starts go to the Connections
page to manually add a new connection by tapping the +
button. Enter a connection name My Service
and a Sponge address, e.g. http://192.168.0.2:1836
. Tap the VERIFY
button to test the connection and then save it by tapping the OK
button. Then tap the My Service
connection.
Depending on your Docker environment you may be able to automatically discover the service in your local network by selecting the Find new nearby services
in the Connections
page.
Now you can remotely call your actions from your Android device.
-
Remote API Security. The Remote Sponge service template is set up only for anonymous users. If you would like to configure other users or change user privileges to knowledge bases, you can provide your own settings and pass the
-Dsponge.securityDir
and-Dpassword.file
parameters to thedocker
commadline.
2.2. Embedded in a Java application
Sponge could be embedded in a Java application by adding a Maven dependency.
2.2.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.16.5</version>
</dependency>
2.2.2. Creating and starting a Sponge engine
The following example shows how to make Sponge a part of a custom Java application.
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.
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.3. Standalone command-line program
Prerequisites:
-
Installed Java 1.8 or above.
-
Environment variable
JAVA_HOME
set orjava
executable placed inPATH
.
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.
|
Download sponge-1.16.5-standalone.zip
.
2.3.1. Linux/MacOS/Unix
First steps:
-
Unpack the archive
unzip -q sponge-1.16.5-standalone.zip
-
Run Sponge example using a configuration file.
cd bin ./sponge -c ../examples/script/py/triggers_hello_world.xml
Output console showsHello 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.
kill -HUP <pid>
See User Guide for limitations of reloading knowledge bases. |
kill -TERM <pid>
2.3.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 showsHello 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.3.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.
./sponge -k ../examples/standalone/trigger_simple.py -i
> 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.
|
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
.
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.
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 Remote API server is configured, you could call this action remotely.
# Call the action to get the JSON response with the result.
curl -i -k -X POST -H "Content-type:application/json" http://localhost:1836/call -d '{"body":{"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/actions -d '{"body":{"name":"HelloWorldAction"}}'
3.1.1. Python
class HelloWorldAction(Action): (1)
def onConfigure(self): (2)
self.withLabel("Hello world").withDescription("Returns a greeting text.") (3)
self.withArg(StringType("name").withLabel("Your name").withDescription("Type your name.")) (4)
self.withResult(StringType().withLabel("Greeting").withDescription("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 Remote API client. |
3 | Sets up the action label 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.
class HelloWorldAction(Action):
def onCall(self, name):
return "Hello World! Hello {}!".format(name)
./sponge -k ../examples/script/py/actions_hello_world.py
Press CTRL+C
to exit the Sponge standalone command-line application.
All Sponge processors can be defined and enabled using processor builders as well. Processor builders provide a more concise code for simple processors.
sponge.enable(ActionBuilder("HelloWorldAction").withOnCall(lambda action, name: "Hello World! Hello {}!".format(name)))
All callouts placed in the source code in the examples below remain the same, because they are functionally equivalent. |
3.1.2. Ruby
class HelloWorldAction < Action (1)
def onConfigure (2)
self.withLabel("Hello world").withDescription("Returns a greeting text.")" (3)
self.withArg(StringType.new("name").withLabel("Your name").withDescription("Type your name.")) (4)
self.withResult(StringType.new().withLabel("Greeting").withDescription("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
.
./sponge -k ../examples/script/rb/actions_hello_world.rb
Press CTRL+C
to exit the Sponge standalone command-line application.
3.1.3. Groovy
class HelloWorldAction extends Action { (1)
void onConfigure() { (2)
this.withLabel("Hello world").withDescription("Returns a greeting text.") (3)
this.withArg(new StringType("name").withLabel("Your name").withDescription("Type your name.")) (4)
this.withResult(new StringType().withLabel("Greeting").withDescription("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
.
./sponge -k ../examples/script/groovy/actions_hello_world.groovy
Press CTRL+C
to exit the Sponge standalone command-line application.
3.1.4. JavaScript
var HelloWorldAction = Java.extend(Action, { (1)
onConfigure: function(self) { (2)
self.withLabel("Hello world").withDescription("Returns a greeting text."); (3)
self.withArg(new StringType("name").withLabel("Your name").withDescription("Type your name.")); (4)
self.withResult(new StringType().withLabel("Greeting").withDescription("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
./sponge -k ../examples/script/js/actions_hello_world.js
Press CTRL+C
to exit the Sponge standalone command-line application.
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
.
3.2.1. Python
class HelloWorldTrigger(Trigger): (1)
def onConfigure(self): (2)
self.withEvent("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
.
./sponge -k ../examples/script/py/triggers_hello_world.py
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. |
3.2.2. Ruby
class HelloWorldTrigger < Trigger (1)
def onConfigure (2)
self.withEvent("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
.
./sponge -k ../examples/script/rb/triggers_hello_world.rb
Press CTRL+C
to exit.
3.2.3. Groovy
class HelloWorldTrigger extends Trigger { (1)
void onConfigure() { (2)
this.withEvent("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
.
./sponge -k ../examples/script/groovy/triggers_hello_world.groovy
Press CTRL+C
to exit.
3.2.4. JavaScript
var HelloWorldTrigger = Java.extend(Trigger, { (1)
onConfigure: function(self) { (2)
self.withEvent("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
./sponge -k ../examples/script/js/triggers_hello_world.js
Press CTRL+C
to exit.
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.
3.3.1. Python
# Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule(Rule): (1)
def onConfigure(self): (2)
self.withEvents(["heartbeat h1", "heartbeat h2 :none"]) (3)
self.withCondition("h2", lambda rule, event: rule.firstEvent.get("source") == event.get("source")) (4)
self.withDuration(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.withEvent("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
.
./sponge -k ../examples/script/py/rules_heartbeat.py
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.
|
3.3.2. Ruby
# Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule < Rule (1)
def onConfigure (2)
self.withEvents(["heartbeat h1", "heartbeat h2 :none"]) (3)
self.withCondition("h2", lambda { |rule, event|
return rule.firstEvent.get("source") == event.get("source")
}) (4)
self.withDuration(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.withEvent("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
.
./sponge -k ../examples/script/rb/rules_heartbeat.rb
Sound the alarm!
Press CTRL+C
to exit.
3.3.3. Groovy
// Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
class HeartbeatRule extends Rule { (1)
void onConfigure() { (2)
this.withEvents(["heartbeat h1", "heartbeat h2 :none"]) (3)
this.withCondition("h2", { rule, event ->
return rule.firstEvent.get("source") == event.get("source")
}) (4)
this.withDuration(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.withEvent("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
.
./sponge -k ../examples/script/groovy/rules_heartbeat.groovy
Sound the alarm!
Press CTRL+C
to exit.
3.3.4. JavaScript
// Sounds the alarm when heartbeat event stops happening at most every 2 seconds.
var HeartbeatRule = Java.extend(Rule, { (1)
onConfigure: function(self) { (2)
self.withEvents(["heartbeat h1", "heartbeat h2 :none"]); (3)
self.withCondition("h2", function(rule, event) {
return rule.firstEvent.get("source") == event.get("source");
}); (4)
self.withDuration(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.withEvent("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
.
./sponge -k ../examples/script/js/rules_heartbeat.js
Sound the alarm!
Press CTRL+C
to exit.