User Guide


Table of Contents

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.

2. Architecture

The figure below presents the architecture of the Sponge system.

engine architecture
Figure 1. 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. Configuration

Sponge may be configured:

  • in an XML configuration file,

  • using the Engine Builder API.

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="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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.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 could be used as Java system properties if the attribute system is set to true. Java system properties passed to the Java Virtual Machine 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 could 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.

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.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.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.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/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.

4. Engine

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/hello_world.xml").build();
engine.startup();

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.

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.

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 may 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.

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).

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(String name, Class<T> cls)

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.

5.3. User variables

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

  • the engine scope,

  • the knowledge base scope.

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.

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)

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.

5.5. Reloading

Sometimes a situation may happen that there will be a need for a dynamic modification of event processors, for example to add a new rule or remove 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 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 will be run in the same interpreter instance.

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.

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.

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.

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.
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.4.2</version>
</dependency>

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.getOrDefault("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.

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.4.2</version>
</dependency>

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.

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.

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.

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

6. 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.

6.1. Properties and methods

Table 9. 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.

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(attributeName, attributeClass)

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 getOrDefault(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.

6.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.

6.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.

6.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.

6.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 10. System events
Event name Description

startup

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

6.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!"

6.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.

6.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 could 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.

  • Using the traditional API (e.g. sponge.send(sponge.makeEvent("helloEvent").set("say", "Hello World!"))).

The EventDefinition fluent API is the preferred option.

An event may be to 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 11. 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.

6.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.

6.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.

7. 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.

7.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.

7.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.

7.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.

7.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")

7.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)

7.4. Properties and methods

Table 12. 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 may 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 may 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 may 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.

Thanks to many scripting languages, properties may 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").

8. 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.

8.1. Properties and methods

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

Table 13. 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(). 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. This method is mandatory. Every action should implement at least one onCall method.

The onConfigure method in actions is not mandatory.

8.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 (in this case).
3 Calls the action named "EchoAction" passing one argument.
Console output
Action returned: test

8.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)

8.4. Argument and result metadata

Actions may have metadata specified in the onConfigure method. Metadata may describe action arguments and an result. Metadata are not used to verify action arguments or an result. They are useful in a client code, for example in a generic GUI that calls Sponge actions.

Metadata for argument and result types support types that 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. The basic properties are texts not interpreted by the Sponge engine but could be interpreted by a client code.

Table 14. Basic type properties
Property Description

nullable

Tells if a value of this type may 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.

tags (or tag)

Optional tags (text labels).

features (or feature)

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

Table 15. Supported argument and result metadata 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

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

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

ListType

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

MapType

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

ActionType

A type that allows using a result of one action to be a type for another action argument or result. For example, assuming that the result type of the action GetAvailableSensorNames is a list of strings and the action returns ["sensor1", "sensor2"], ActionType("GetAvailableSensorNames") means that a value of this type may be "sensor1" or "sensor2". You may customize this behavior in your application by setting an arbitrary format string for this ActionType, for example for allowing multi or single choice. The handling of ActionType (e.g. calling GetAvailableSensorNames action) must be implemented in a client code.

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.")]
        self.resultMeta = ResultMeta(StringType()).displayName("Upper case text")
    def onCall(self, text):
        return text.upper()
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.TestCompoundComplexObject").tag("complex")),
            ArgMeta("javaClassListArg", ListType(ObjectType("org.openksavi.sponge.examples.TestCompoundComplexObject"))),
            ArgMeta("binaryArg", BinaryType().mimeType("image/png").tags(["drawing", "handwritten"]).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.

8.5. 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).

9. 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 16. Event processors
Event processor Singleton

Filter

Yes

Trigger

Yes

Rule

No

Correlator

No

When configuring an event processor, each event name may 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:.

9.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.

9.1.1. Properties and methods

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

Table 17. 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.

9.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.getOrDefault("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.

9.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 may be enabled only manually, for example in a script knowledge base e.g.:

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

9.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.

9.2.1. Properties and methods

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

Table 18. 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.

9.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.

9.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)

9.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 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 instances of the rule, each created automatically for every event that could be accepted as the first event of the rule.

9.3.1. Properties and methods

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

Table 19. 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(eventSpecifications) 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 a condition method is irrelevant.

addAllConditions(conditions)

Adds conditions for all events.

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.

9.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 20. 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".

9.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 21. 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.

9.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.

9.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.

9.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)

9.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)

9.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.

9.4.1. Properties and methods

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

Table 22. 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.

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.

9.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.

9.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)

10. 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.

10.1. Properties and methods

Table 23. 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.

10.2. Example in Java

Definition of the plugin in the XML configuration file
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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.

10.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

10.4. Example in a script language

Definition of the plugin in the XML configuration file
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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

10.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.

11. 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.

12. Embedding Sponge in custom applications

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

12.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.4.2</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.4.2</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>

13. Integration

13.1. Spring framework

Sponge engine may 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 may 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();
}

13.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.4.2</version>
</dependency>

13.2. Apache Camel

13.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).

13.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.4.2</version>
</dependency>

13.2.3. URI format

sponge:engineRef[?options]

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

13.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.

13.2.5. Sponge support for Camel

13.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="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://sponge.openksavi.org http://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 24. Important CamelPlugin properties and methods
Property / Method Description

send(body)

Sends the body to all current consumers.

send(uri, body)

Sends the body to an endpoint.

request(uri, body)

Sends the body to an endpoint returning any result output body.

getContext()

Returns a Camel context.

getConsumers()

Returns the current list of consumers.

For more information see the CamelPlugin Javadoc.

13.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.

13.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 camel = context.getBean(CamelContext.class);
ProducerTemplate producerTemplate = camel.createProducerTemplate();
producerTemplate.sendBody("direct:start", "Send me to the Sponge");

// Waiting for the engine to process an event.
Output console
Send me to the Sponge
13.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 may be used by the next endpoint in the route if there is any.

To avoid misconception please note that events in the Output Event Queue are not sent to the Camel route.
13.2.6.1.1. 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.

13.2.6.1.2. 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}");

13.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.send(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.send("direct:log", event.get("message"))

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

13.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())

13.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 may be used in knowledge bases) is restApiServer.

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

autoStart

boolean

If true then the REST service will start when the plugin starts up. The default value is true.

restComponentId

String

The Camel REST component id. The default value is "jetty".

host

String

The REST API host.

port

int

The REST API port. The default REST API port is 1836.

prettyPrint

boolean

The pretty print option.

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. The default value is false.

routeBuilderClass

String

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

apiServiceClass

String

The RestApiService implementation class name. The default implementation is DefaultRestApiService.

securityServiceClass

String

The RestApiSecurityService implementation class name. The default implementation is 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. The default value is 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 library.

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

13.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.4.2</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.

13.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 26. 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 and those that have argument and result metadata specified in their configuration (this may be changed by setting the request property metadataRequired to false).

Call an action

call

Calls an action.

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.

13.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.

13.3.4. Requests and responses

Each request may contain base properties.

Table 27. 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. 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. May be used in a user/password authentication mode.

authToken

No

An authentication token. 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 '{"nameRegExp":".*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

13.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.

13.3.5.1. Authentication mode

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

Table 28. 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.

13.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.

13.3.6. HTTPS

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

13.3.6.1. Sponge REST API
13.3.6.1.1. Overview
Version information

Version : 1

URI scheme

Host : 0.0.0.0:1836
BasePath : /
Schemes : HTTP

Tags
  • sponge.json/v1 : Sponge REST API

13.3.6.1.2. Paths
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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

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

Consumes
  • application/json

Produces
  • application/json

Tags
  • sponge.json/v1

13.3.6.1.3. Definitions
GetActionsRequest

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

metadataRequired
optional

Metadata required

boolean

nameRegExp
optional

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

string

Type
Name Schema

kind
optional

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

format
optional

string

tags
optional

< string > array

features
optional

< string, object > map

defaultValue
optional

object

nullable
optional

boolean

KnowledgeBaseMeta

Represents a Sponge 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

Represents a Sponge 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

features
required

The action features

< string, object > map

argsMeta
optional

The action arguments metadata

< ActionArgMeta > array

resultMeta
optional

The action result metadata

ActionArgMeta

Represents a Sponge action argument metadata

Name Description Schema

name
required

The action argument name

string

type
required

The action argument type

displayName
required

The action argument display name

string

description
required

The action argument description

string

GetActionsResponse

Get actions response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

actions
required

The Sponge actions

< ActionMeta > array

ActionResultMeta

Represents a Sponge action result metadata

Name Description Schema

type
required

The action result type

displayName
required

The action result display name

string

description
optional

The action result description

string

ActionCallRequest

Represents a Sponge action call

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

Action call response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

result
required

The action result

object

GetKnowledgeBasesRequest

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

Get knowledge bases response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

knowledgeBases
required

The Spoonge knowledge bases

< KnowledgeBaseMeta > array

LoginRequest

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

Login response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

authToken
required

The authentication token

string

LogoutRequest

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

Logout response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

ReloadRequest

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

Reload response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

SendEventRequest

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

Send event response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

eventId
required

The event id

string

GetVersionRequest

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

Get version response

Name Description Schema

errorCode
optional

The error code

string

errorMessage
optional

The error message

string

detailedErrorMessage
optional

The detailed error message

string

id
optional

The corresponding request id

string

version
required

The Sponge version

string

13.3.7. Environment

13.3.7.1. Standalone

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

13.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.

13.4. Sponge REST API client

The Sponge REST API client 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 example
SpongeRestApiClient client = new DefaultSpongeRestApiClient(RestApiClientConfiguration.builder()
        .url(String.format("http://localhost:%d/%s", PORT, RestApiConstants.DEFAULT_PATH))
        .build()); (1)
String upperCaseText = client.call(String.class, "UpperCase", "text"); (2)
1 Create a new REST API client.
2 Call the remote action.
REST API client for a named user example
SpongeRestApiClient client = new DefaultSpongeRestApiClient(RestApiClientConfiguration.builder()
        .url(String.format("http://localhost:%d/%s", PORT,RestApiConstants.DEFAULT_PATH))
        .username(username)
        .password(password)
        .build());

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

There is also the implementation that uses the Spring (version 5) RestTemplate: SpringSpongeRestApiClient.

13.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.4.2</version>
</dependency>

In case of using SpringSpongeRestApiClient you should also add the following dependency to your pom.xml:

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-http</artifactId>
</dependency>

13.5. Run an external executable as a subprocess

Sponge provides a way 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.

Table 29. 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. May be null (the default value) if the thread shouldn’t wait.

redirectType

RedirectType

The redirect type (see the following table). The default value is INHERIT.

charset

String

The charset the subprocess output stream used if the redirectType is STRING or LOGGER.

waitForOutputLineRegexp

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. May be null if the thread shouldn’t wait for a specific line (or waitForErrorOutputLineRegexp if set).

waitForErrorOutputLineRegexp

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.

waitForOutputLineTimeout

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.

Table 30. Redirect type
Value Description

LOGGER

Logs the subprocess standard output (as INFO) and error output (as WARN) to the logger.

INHERIT

Sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.

STRING

Writes all subprocess standard output and error 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 and error output to the ProcessInstance.outputBinary byte array. The thread that started the subprocess will wait for the subprocess to exit.

NONE

No redirection will be set.

Example of running an external executable with arguments
ProcessInstance process = SpongeUtils.startProcess(engine,
        ProcessConfiguration.builder("echo").arguments("TEST").redirectType(RedirectType.STRING).build());
String output = process.outputString();
Example of running an external executable with an additional environment
ProcessInstance process = SpongeUtils.startProcess(engine, ProcessConfiguration.builder("printenv")
        .arguments("TEST_VARIABLE").env("TEST_VARIABLE", "TEST").redirectType(RedirectType.STRING).build());

13.6. 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.

13.6.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.4.2</version>
</dependency>

13.6.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 31. 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 may 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.

13.6.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().

13.6.2.2. ClientServerPy4JPlugin

ClientServerPy4JPlugin provides integration with CPython using Py4J ClientServer.

Table 32. 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)

13.6.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>
                    <redirectType>LOGGER</redirectType>
                </pythonScript>
                <pythonScriptBeforeStartup>false</pythonScriptBeforeStartup>
            </configuration>
        </plugin>
    </plugins>

13.7. 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 may 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>

13.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-reactivex</artifactId>
    <version>1.4.2</version>
</dependency>

13.8. 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 may be used in knowledge bases) is midi.

Table 33. 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.

13.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-midi</artifactId>
    <version>1.4.2</version>
</dependency>

13.9. 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 may 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 34. 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, 2018, ".*(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.

13.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-mpd</artifactId>
    <version>1.4.2</version>
</dependency>

13.10. 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 may 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>

13.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-rpi-pi4j</artifactId>
    <version>1.4.2</version>
</dependency>

13.11. 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 may 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>

13.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-grovepi</artifactId>
    <version>1.4.2</version>
</dependency>

13.12. 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.

13.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-tensorflow</artifactId>
    <version>1.4.2</version>
</dependency>

13.12.2. The MNIST 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/mnist/requirements.txt
(tensorflow)$ deactivate
Table 35. The main components of the MNIST REST service example
File name Description

MnistService.java

The Java interface of the MNIST Python service. This interface is used by Py4J to expose Python functionality to a Java process.

mnist_library_predict.py

The predefined Sponge knowledge base library (compatible with Jython) for the MNIST example that contains definitions of actions that will be exposed in the Sponge REST API service. The MnistPredict action takes a binary representation of a PNG file and passes it to the running Python script file by invoking MnistService.predict(byte[] image) method. This method will be invoked on the remote object running in the Python process.

MnistRestServerMain.java

The main Java class, that starts up Sponge.

mnist_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 REST API server.

mnist_rest_server.py

The main Sponge knowledge base file (compatible with Jython) for that example.

python/mnist_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/mnist_service.py

The Python script file (compatible with CPython) that loads the model. If the model file data/mnist_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 MnistService implementation that is exposed by the Python-side Py4J gateway.

MnistRestClientMain.java

The main Java class for the simple client that invokes the remote MnistPredict action using the REST API to recognize the sample digit image. It should print the text: Recognized digit for image file examples/tensorflow/mnist/data/1_0.png is 1.

python/mnist_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.

14. 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.

14.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.

14.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.

14.3. Processors

When defining a processor that is not a singleton, its class implementation should provide lightweight creating of new instances.

14.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.

14.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.

14.6. Rules

Rules should be used when triggers functionality is not sufficient.

14.7. Correlators

Correlators should be used when filters, triggers and rules functionality is not sufficient for the problem you try to solve.

14.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.

15. Scripting languages

15.1. Supported scripting languages

Table 36. Supported knowledge base scripting languages
Language Implementation

Python

Jython

Ruby

JRuby

Groovy

Groovy

JavaScript

Nashorn

15.2. Python

15.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.

15.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.4.2</version>
</dependency>

The dependency for Jython used in Sponge is Jython shaded.

15.3. Ruby

15.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.4.2</version>
</dependency>

15.4. Groovy

15.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.

15.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.4.2</version>
</dependency>

15.5. JavaScript

JavaScript interpreter supports shell scripting extensions in Nashorn to provide simpler shell integration.

15.5.1. Limitations

15.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.
15.5.1.2. Abstract processors

The support for abstract processors is not implemented for processors written in JavaScript.

15.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;
        }
    }
});

15.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.4.2</version>
</dependency>

16. 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.

17. Examples

17.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.

17.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="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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;
    }
}

17.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="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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):
        # Send the alarm message to all Camel endpoints that use the engine as a consumer.
        camel.send(event.get("message"))

        # Send the alarm message to a specific endpoint.
        camel.send("direct:log", event.get("message"))

        sponge.getVariable("alarmForwarded").set(True)

17.1.3. REST API Demo Service

The Demo Service example 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 MNIST 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 MNIST 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
mnist.home=/opt/tomcat/sponge
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 may 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

17.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 37. 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.

hello_world (py, rb, <groovy, js, XML configuration)

Hello world 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.

17.3. Features examples

The features examples show how to use some of Sponge features. They are not implemented in all supported scripting languages.

Table 38. 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.

17.4. Standalone examples

The standalone examples show how to use some of Sponge features in the standalone command-line application.

Table 39. 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.

18. Maven artifacts

The groupId of Sponge Maven artifacts is org.openksavi.sponge.

Table 40. 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 41. Sponge optional external Maven artifacts
ArtifactId License Central Maven Repository Description

sponge-mpd

GNU GPL 3.0

Yes

MPD (Music Player Daemon) integration.

19. 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).

19.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.

19.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/hello_world.xml

# Run with the knowledge base named 'helloWorldKb' using the specified knowledge base file.
./sponge -k helloWorldKb=../examples/script/py/hello_world.py

# Run with the knowledge base named 'kb' using the specified knowledge base file.
./sponge -k ../examples/script/py/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

19.3. Environment variables

Optionally you may set the environment variable SPONGE_HOME.

Linux/MacOS/Unix
cd root/sponge-standalone
export SPONGE_HOME=`pwd`
Windows
cd root/sponge-standalone
set SPONGE_HOME=%cd%

19.4. Standalone plugin configuration parameters

Table 42. 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.

19.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="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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.

19.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.

19.6.1. Spring XML configuration

Example of Spring configuration in StandalonePlugin
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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>

19.6.2. Spring Groovy configuration

Spring container plugin in Sponge configuration file example
<?xml version="1.0" encoding="UTF-8"?>
<sponge xmlns="http://sponge.openksavi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://sponge.openksavi.org http://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)
}

19.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)

19.7. Logging and exception reporting

19.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>

19.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.

19.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 may 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.

19.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

19.10. Directory structure

Table 43. 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.

19.11. Camel components and data formats available out of the box

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 44. Camel components out of the box
Component Description

camel-amqp

AMQP

camel-bean-validator

Validation

camel-dns

DNS

camel-docker

Docker

camel-dropbox

Dropbox

camel-ejb

EJB

camel-eventadmin

OSGi EventAdmin events

camel-exec

Executing system commands

camel-facebook

Facebook

camel-ftp

FTP

camel-geocoder

Geocoder

camel-grape

Grape

camel-http4

HTTP

camel-mail

Mail

camel-irc

IRC

camel-jbpm

jBPM

camel-jdbc

JDBC

camel-jms

JMS

camel-jmx

JMX

camel-jsch

SCP

camel-ldap

LDAP

camel-linkedin

LinkedIn

camel-mqtt

MQTT

camel-mustache

Mustache

camel-netty4

Netty

camel-netty4-http

Netty HTTP

camel-olingo2

OData 2.0 services using Apache Olingo 2.0

camel-paho

Paho/MQTT

camel-pdf

PDF

camel-pgevent

PostgreSQL events, LISTEN/NOTIFY

camel-printer

Printer

camel-quartz2

Quartz

camel-rabbitmq

RabbitMQ

camel-rss

RSS

camel-smpp

SMPP

camel-snmp

SNMP

camel-sql

SQL

camel-ssh

SSH

camel-stomp

STOMP

camel-stream

Input/output/error/file stream

camel-twitter

Twitter

camel-velocity

Velocity

camel-vertx

Vert.x

camel-weather

Open Weather Map

camel-websocket

Websocket

camel-xmpp

XMPP/Jabber

camel-saxon

XQuery template

Table 45. Camel data formats out of the box
Data format Description

camel-xstream

XStream

camel-jackson

JSON

camel-soap

SOAP

camel-csv

CSV

camel-tarfile

Tar format

camel-crypto

Java Cryptographic Extension

camel-syslog

Syslog

camel-ical

ICal

camel-barcode

Barcodes (QR-Code, etc.)

20. Third party software

Sponge uses or supports third party software released under various open-source licenses.

Table 46. Key third party software
Package License Description

Apache Commons Configuration

Apache 2.0

Used for reading configuration files.

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 47. 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.