Writing EPL plug-ins in Java
EPL plug-ins can be written in Java. This section describes the steps required to develop and deploy EPL plug-ins written in Java.
EPL plug-ins can be written in Java. This section describes the steps required to develop and deploy EPL plug-ins written in Java.
Create a public class for your plug-in. To mark the class as a plug-in, add the @com.apama.epl.plugin.annotation.EPLPlugin
annotation. Specify the import name
to be used from EPL, and a description
for human consumption. For example:
@com.apama.epl.plugin.annotation.EPLPlugin(
name="TestPlugin",
description="A test plugin"
)
public class TestPlugin
{
...
}
Add one or more public static
methods to the plug-in class that should be callable from EPL. These methods must match the permitted signatures described in Permitted signatures for methods. All calls from an Apama application are made to these static methods from all contexts. As the plug-in author you are responsible for ensuring correct concurrency.
Compile the source code for your plug-in class(es), including lib/ap-correlator-extension-api.jar
on the classpath so that the annotations can be found and processed. Package the compilation output into a JAR file (including the META-INF/jmon-jar.xml
descriptor file that is automatically generated from the annotations alongside the classes), ready for including in the YAML initialization list for your project. You can perform these steps using a build script, or directly from the command line:
cd src
javac -classpath "$APAMA_HOME/lib/ap-correlator-extension-api.jar" -d ../bin *.java
cd ../bin
jar -cf ../MyPlugin.jar *
Each Java plug-in JAR is loaded in its own dedicated Java classloader, which by default has access only to its own classes, and those available globally in the correlator’s system classloader (which includes some standard Apama libraries such as the ap-correlator-extension-api.jar
and ap-util.jar
).
If your plug-in JAR depends on any third-party libraries, configure them by adding an @ApplicationJar
annotation. This annotation can be added into one (but not more than one) of the Java source files in your JAR:
@com.apama.epl.plugin.annotation.ApplicationJar(
name = "MyApplicationJar",
classpath = "${env:APAMA_PROJECT_DIR}/lib/foo.jar;${env:MY_THIRD_PARTY_DIR}/lib/bar.jar"
)
The name
is used to identify this JAR in log messages and in case you need to use engine_delete
.
The classpath
string consists of any number of classpath entries, delimited by the semicolon ;
character. Note that the semicolon should be used even on platforms that typically use a colon or other character to separate path entries.
Avoid using absolute paths in the classpath, as this makes it difficult to use the plug-in JAR on different machines. Instead, use ${...}
placeholders to identify the first part of each path, for example, the installation directory of a third party whose libraries you wish to use. Currently, two types of placeholder are supported:
${env:MY_ENV_VAR_NAME}
is replaced by an environment variable called MY_ENV_VAR_NAME
${sys:MY_SYS_PROP_NAME}
is replaced by a Java system property called MY_SYS_PROP_NAME
If your YAML configuration file is located in the project root directory and defines an environment variable APAMA_PROJECT_DIR
with the value ${PARENT_DIR}
you can access the project directory using that environment variable, for example: ${env:APAMA_PROJECT_DIR}/lib/foo.jar;${env:APAMA_PROJECT_DIR}/lib/bar.jar
. See Setting environment variables for Apama components.
If needed, the values for Java system property placeholders can be specified on the correlator command line as: -J-DMY_SYS_PROP_NAME=path
The correlator logs a warning for any path that cannot be found, but fails to inject the plug-in entirely if the classpath includes any ${...}
placeholders that are not defined.
For a method to be exposed to EPL, it must be public, must be static and every argument plus the return type must be one of the following:
Java Entry |
EPL Type |
Notes |
---|---|---|
|
|
Truncated when passed in, for compatibility. |
|
| |
|
|
Copy in / copy out. |
|
| |
|
| |
|
|
Is either |
|
|
Passing in either NaN or infinity throws an exception that kills the monitor instance if not caught. Deprecated, use |
|
|
New type defined for plug-ins. |
|
|
New type defined for plug-ins. |
|
|
Any Java object can be held in EPL via a chunk. |
|
|
Any above type except |
|
N/A |
Permitted as a return type only. |
Any method not matching this signature is ignored and logged at DEBUG
.
Any function with multiple overloads is ignored (none of them are exposed) and this is logged once at WARN
and once per method at DEBUG
.
This Apama release uses Java version 17.
After you create an EPL plug-in in Java, it must be injected into a Java-enabled correlator before it is available for use in Apama applications. Applications that will use the plug-in also need to import the plug-in by name.
To use Java EPL plug-ins, the correlator must be started with the --java
(or -j
) option.
To inject the JAR into the correlator, add it to the initialization
section of the YAML configuration file or inject the plug-in JAR file from the command line with the -j
(--java
) option:
engine_inject –j plugin_name.jar
Each Java plug-in JAR is loaded into its own separate classloader. This means that they have no access to any classes loaded in other JAR files. If your plug-in requires any other Java libraries, they must be listed in the classpath
with an ApplicationJar
annotation, included in the correlator’s global classpath, or injected in the same plug-in JAR file as the plug-in. See Configuring the classpath and application name for EPL plug-in JARs for more details.
Once a Java plug-in has been injected it is available for import using the name
(including the package) defined in the @ApplicationJar
annotation. The correlator will automatically introspect the class and make available any suitable, public methods that can be called directly from EPL. For example, the following code imports a plug-in named mypackage.TestPlugin
and calls its dosomething
method:
monitor m {
import "mypackage.TestPlugin" as test;
action onload()
{
test.dosomething();
}
}
Note, if the plug-in JAR file has been incorrectly injected, the correlator will try to load the plug-in as a C++ plug-in and may give an error such as Error opening plug-in library libfoo.so: libfoo.so: cannot open shared object file: No such file or directory
. If this happens and you were trying to load a plug-in written in Java, then check that the JAR file was created and injected correctly before your EPL file was injected.
An EPL plug-in can be explicitly deleted by calling engine_delete
with the application name defined in the @ApplicationJar
annotation. EPL monitors using the plug-in depend on the plug-in type in the normal fashion. The plug-in will not be deleted until the application and all dependent monitors are deleted.
As each plug-in is loaded in its own classloader, once the application has been deleted, the plug-in can be re-injected and it will be loaded into a new classloader.
Deleting a plug-in written in Java can only be done when it is not used by any EPL monitors. If the --force
option of the engine_delete
tool is used, then all monitors importing the plug-in are also deleted.
EPL plug-ins can be passed context objects using the com.apama.epl.plugin.Context
type. The Context
object is defined as:
package com.apama.epl.plugin;
public class Context
{
public String toString();
public Context();
public String getName();
public int hashCode();
public boolean isPublic();
public boolean equals(Context other);
public static native Context getCurrent();
}
The getCurrent
method returns the context that this method was called from.
EPL plug-ins can use the com.apama.epl.plugin.Correlator
class to send an event, subscribe to a channel, or to specify blocking behavior. The Correlator
class is defined as:
package com.apama.epl.plugin;
public class Correlator
{
public static native void sendTo(String evt, String chan);
public static native void sendTo(String evt, Context ctx);
public static native void sendTo(String evt, Context[] ctxs);
public static native void sendTo(String evt, Channel c);
public static native void subscribe(EventHandler handler, String[] channels);
public static native void unsubscribe(EventHandler handler, String[] channels);
public static native void unsubscribe(EventHandler handler);
public static native void pluginMethodBlocking();
}
The Correlator
methods are:
sendTo(String, String)
– Sends the event represented in the first String
to the channel specified in the second String
. Any contexts and external receivers that are subscribed to the specified channel receive the event. If there are no subscribers the event is discarded.sendTo(String, Context)
– Sends the event represented in String
to the context referred to by the com.apama.epl.plugin.Context
argument. An exception is thrown if the context reference is invalid.sendTo(String, Context[])
– Sends the event represented in String
to the array of contexts referred to by the com.apama.epl.plugin.Context[]
argument. If one context reference is invalid an exception is thrown and the event is not sent to any context.sendTo(String, Channel)
— Sends the event represented in String
. If the specified com.apama.epl.plugin.Channel
object contains a string then the event is sent to the channel that has that name. If Channel
contains a context then the event is sent to that context.subscribe(EventHandler, String[])
– Subscribes the handler object to the channels listed in the string array. If the handler is already subscribed to some channels then the channels listed in the array are added to the list of existing subscriptions. Subscribing to the same channel multiple times results in a single subscription. However, to completely remove a channel subscription that has been added multiple times you must unsubscribe from that channel the same number of times that it was subscribed to.unsubscribe(EventHandler, String[])
– For the channels specified in the string array, this method removes the subscriptions from the specified handler. It is possible for the result of this method to be that the handler is not subscribed to any channels. Unsubscription from a channel that the handler is not subscribed is ignored.unsubscribe(EventHandler)
– Removes all subscriptions from the specified handler. If this handler is not subscribed to any channels the method is ignored.pluginMethodBlocking()
– Informs the correlator that the plug-in is potentially blocking for the rest of this call and the correlator is free to spin up additional threads on which to run other contexts.For more information on com.apama.epl.plugin.Context
and com.apama.epl.plugin.Correlator
, see the API reference for Java (Javadoc).
A Java plug-in can register callbacks to receive events that are sent to named channels. This is similar to the monitor.subscribe()
method in EPL. Events are delivered in string form by means of a method on a known interface.
To register a callback, the plug-in must define a class that implements the com.apama.epl.plugin.EventHandler
interface:
public interface EventHandler
{
void handleEvent(String event, String channel);
}
The handleEvent()
method is called once for each event sent to a channel that this handler is subscribed to, with the channel on which it was received. To manage EventHandler
object channel subscriptions, use the subscribe()
and unsubscribe()
methods on com.apama.epl.plugin.Correlator
. When a handler is unsubscribed from all channels any in-progress callbacks will complete, but no further callbacks will be made to that handler.
Similar to context objects, you can pass EPL com.apama.Channel
objects into a Java plug-in. The equivalent Java class is com.apama.epl.plugin.Channel
and you can use objects of this class to send events to channels. Like the EPL Channel
type, the Java Channel
class has three constructors:
Channel (String name)
Channel (com.apama.epl.plugin.Context c)
Channel ()
A Channel
object can contain a string that is the name of a channel or it can contain a context. The no-argument constructor creates a Channel
object that contains an empty context. If you try to send an event to an empty context the sendTo()
method throws an exception.
You can call the empty()
method on a Java Channel
object. It returns true
only if the object contains an empty context.
If a method throws an exception, that exception is passed up to the calling EPL and can be caught by the calling monitor. If an exception is not caught it will terminate the monitor instance. Details on catching exceptions in EPL can be found in Exception handling.
If a Java plug-in throws a java.lang.RuntimeException
, or subclass, which is in the java.
namespace (for example, java.lang.NullPointerException
) then it will be logged at ERROR
with a stacktrace before being rethrown. Unchecked exceptions from other sources (for example client exception types) will not be logged.
If a plug-in needs to run anything when it is loaded, you can do this in a static initializer:
public class Plugin
{
static {
... // initialization code here
}
}
It is not natively possible for a plug-in to run anything when it is unloaded. If you need this functionality you can declare a method to be called when the plug-in is unloaded using annotations:
public class Plugin
{
@com.apama.epl.plugin.annotation.Callback(
type=com.apama.epl.plugin.annotation.Callback.CBType.SHUTDOWN)
public static void shutdown()
{
... // shutdown code here
}
}
The method must be a public static function which takes no arguments and returns void. Currently, Apama does not support callbacks other than SHUTDOWN
.
In a correlator some threads have the potential to block and others do not. If a thread might block, the correlator starts new threads if it has additional runnable contexts. By default the correlator assumes that a plug-in call may block and will start additional threads on which to run other contexts. In situations where the plug-in call can never block, the additional overhead of starting new threads when all CPUs are busy is unnecessary. If you know that a plug-in or an individual method is non-blocking, you can improve efficiency by annotating either entire plug-ins or individual methods as non-blocking.
Note, however, if a method declared as non-blocking does block, the correlator can block all threads waiting for them to finish, resulting in a deadlocked correlator. For methods that are normally non-blocking, but may block in predictable situations, see “Sometimes-blocking functions” below.
Annotations. You can apply the annotation @com.apama.epl.plugin.annotation.NoBlock
with no arguments to either a plug-in class, or to a method on a class:
@com.apama.epl.plugin.annotation.NoBlock()
public class Plugin
{
...
}
When applied to a class, the annotation indicates that no method on the plug-in can ever block.
public class Plugin
{
@com.apama.epl.plugin.annotation.NoBlock()
public static String getValue() {... }
}
When applied to a method, the annotation indicates that this method will never block, but other methods may block.
Sometimes-blocking functions. If you have a function that usually will not block, but under some known conditions may block, then the method can be declared as NoBlock
as long as it then uses a callback to indicate when it is starting the potentially-blocking behavior. The callback is a static method on com.apama.epl.plugin.Correlator
called pluginMethodBlocking
. This function takes no arguments, returns no value and is idempotent. When it is called, the correlator will then assume that the plug-in is potentially blocking for the rest of this call and is free to spin up additional threads on which to run other contexts.
public class Plugin
{
@com.apama.epl.plugin.annotation.NoBlock()
public static String getValue()
{
if (null != localValue) return localValue;
else {
com.apama.epl.plugin.Correlator.pluginMethodBlocking();
localValue = getRemoteValue();
return localValue;
}
}
}
EPL plug-ins written in Java can log to the correlator’s log file, using the open-source SLF4J API.
Each plug-in must create a static instance of Logger
, which provides methods such as debug(...)
, info(...)
, warn(...)
and error(...)
. These result in a message at that log level in the correlator log file. See also Setting correlator and plug-in log files and log levels in a YAML configuration file.
For more information on using the Logger
class, including how to override the default log level, see the API reference for Java (Javadoc).
The following is an example of logging in an EPL plug-in:
package test;
public class MyPlugin
{
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("plugins.test.MyPlugin");
public static void foo()
{
logger.info("A string that's logged at INFO");
}
}
This will produce entries in the correlator log file like this:
2019-06-24 14:22:21.974 INFO [1167792448:processing] - <test.MyPlugin> A string that's logged at INFO
It is recommended to put “plugins.” at the beginning of the category name (as shown in the above example).
If your plug-in uses a third-party library that logs with SLF4J or Log4j 2, then the log output goes to the main correlator log file automatically.
When using a library which uses some other logging implementation, such as the JDK logger, or Apache Java commons logging (JCL), then add a bridging JAR to convert it to SLF4J where possible.
Apama provides sample EPL plug-ins written in Java, located in the correlator_plugin/java
directory of the Apama Samples repository. The samples are:
SimplePlugin
– a basic plug-in with one method that takes a string, and returns another string.ComplexPlugin
– a plug-in that has several methods and handles more complex types.SendPlugin
– a plug-in that demonstrates passing contexts around and sending events.SubscribePlugin
– a plug-in that shows how to subscribe to receive events sent on a particular channel.The samples include Java code, EPL code for the Apama applications that call each of the plug-ins, and an Ant build.xml
file for building all of the samples. See the README.md
in that directory for more information.