Apama currently provides developers with two options to write Java applications:
- Apama’s in-process API for Java, called JMon
- EPL plug-ins written in Java, also called Java plug-ins
JMon, however, is now deprecated and will be removed in a future release. This guide helps you to port your JMon applications. There are various options that you can choose from and they are described below.
Option 1: Port JMon application entirely to EPL
EPL is more powerful and easier to use for most of the use cases.
Consider the following JMon application class Simple
which implements the onLoad()
method of the Monitor
interface and sets up a single event expression looking for all stock trade events with a trade price greater than 10.0. This class instantiation is added as a match listener to the event expression. By implementing the match()
method of the MatchListener
interface, it then extracts the Tick
events that caused the event expressions to trigger and emits the event onto the default channel.
JMon application code:
import com.apama.jmon.*;
public class Simple implements Monitor, MatchListener {
...
public void onLoad() {
EventExpression eventExpr = new EventExpression("all Tick(*, >10.0):t");
eventExpr.addMatchListener(this);
}
public void match(MatchEvent event) {
Tick tick = (Tick)event.getMatchingEvents().get("t");
tick.emit();
}
}
This functionality can easily be written in EPL as follows:
monitor Simple {
action onload() {
on all Tick(*, >10.0):t {
emit t;
}
}
}
Option 2: Port JMon application to EPL and an EPL plug-in written in Java
If you do not want to port your JMon application completely to EPL, a common pattern for writing the main logic and event expressions for a complex event processing (CEP) application is to combine EPL and Java. The EPL code can call an EPL plug-in written in Java (also called “Java plug-in” in this guide) as needed for specific operations, such as invoking a third-party library. There are several design patterns that you can choose from depending on your requirements. All of these design patterns are discussed below with code samples.
When to use these design patterns:
- If an application performs complex operations and uses third-party programming libraries of functions and classes that you want to reuse, porting such complex code to EPL would be an unnecessary, lengthy, expensive and error-prone task. In such cases, these design patterns can be used.
- The operations you need can be performed more easily and efficiently in another language than in EPL. For example, you need to use data structures that cannot be easily represented in EPL.
Design pattern 1: EPL plug-in
In this pattern, all the listeners and lifetime management logic are in EPL, and specific functions are called in the Java plug-in.
Consider the following sample Java monitor which is used to notify users when a stock trade price falls below a given price floor.
import com.apama.jmon.*;
public class StockWatch implements Monitor {
// The limit event used to instantiate a StockWatch worker.
private Limit limit;
// The event expressions and match listeners used in the class instantiation.
private EventExpression dieExpr = null;
private EventExpression fallExpr = null;
/**
* No argument constructor used by the JMon framework on application loading.
* Objects instantiated with this constructor become top-level or factory monitors,
* looking for limit events to denote a request to start monitoring a stock for a
* price floor crossing.
*/
public StockWatch() {}
/**
* Construct an instantiation of the class to start monitoring a stock for a
* price floor crossing. The constructor uses the Limit event to determine the ID
* of the user interested in the price fall, the stock name to monitor, and the value
* of the price floor. The constructor sets up event expressions and match listeners as
* anonymous inner classes to remove the object on receipt of another Limit event by the
* same user for the same stock, and to notify the user when the price floor is crossed.
*
* @param limit The Limit event used to start monitoring a stock
*/
public StockWatch(Limit limit) {
this.limit = limit;
// Set up event expression and match listener to effectively remove
// this object when another Limit event is seen for the same user ID
// and stock name.
dieExpr= new EventExpression("com.apama.samples.stockwatch.Limit(" + limit.userID + ", \"" + limit.stockName + "\", *)");
dieExpr.addMatchListener(new MatchListener () {
public void match(MatchEvent event) {
die();
}
});
// Set up event expression and match listener to monitor for the
// price floor crossing.
fallExpr= new EventExpression("com.apama.samples.stockwatch.Tick(\"" + limit.stockName + "\", <" + limit.priceFloor + "):tick");
fallExpr.addMatchListener(new MatchListener () {
private static final long serialVersionUID = 31L;
public void match(MatchEvent event) {
Tick tick = (Tick)event.getMatchingEvents().get("tick");
Match match = new Match(StockWatch.this.limit.userID, StockWatch.this.limit.stockName, tick.price);
match.emit();
die();
}
});
}
/**
* Implementation of the monitor interface onLoad method. Sets up a top-level
* event expression to create new StockWatch instantiations to monitor for
* price floor crossings.
*/
public void onLoad() {
EventExpression factory = new EventExpression("all com.apama.samples.stockwatch.Limit(*,*,>0.0):limit");
factory.addMatchListener(new MatchListener() {
private static final long serialVersionUID = 30L;
public void match(MatchEvent event) {
new StockWatch( (Limit)event.getMatchingEvents().get("limit") );
}
});
}
// Helper method to mark this object for garbage collection. Removes all match listeners
// added to the event expressions, causing the correlator to drop its references to them.
// Once done, there is no path to the stockwatch object (previously root -> correlator ->
// match listeners -> stockwatch), meaning it can be marked for garbage collection.
private void die(){
if (dieExpr != null) dieExpr.removeAllMatchListeners();
if (fallExpr != null) fallExpr.removeAllMatchListeners();
}
The above JMon application can be re-written as EPL and a Java plug-in. In the following EPL example, all the listeners as well as spawning and termination of monitor instances is in EPL. When a Limit
event is received, the monitor is spawned. The spawned instance then listens for the Limit
event matching the user ID and stock name and calls die
when it receives it. It also creates listeners for all Tick
events with the matching stock name and calls the Java plug-in MyJavaPlugin when an event is received and terminates the monitor instance. The Java plug-in has the logic to handle the matched event.
EPL example (StockWatch.mon):
monitor StockWatch {
// Load the Java plug-in
import "MyJavaPlugin" as plugin;
action onload(){
on all Limit(*,*,>0.0):limit {
spawn stockwatch(limit);
}
}
action stockwatch(Limit limit) {
on all Limit(limit.userId, limit.stockName, *) {
die;
}
on all Tick(limit.stockName, <limit.priceFloor): tick {
plugin.handleMatch(...);
die;
}
}
}
There are various advantages of using this design pattern:
- By setting up listeners in the EPL code, you can use Apama’s event expressions to detect patterns of events, event templates to filter events based on field values, or you can listen for events of a particular type.
- You can call Java plug-in functions directly from the EPL code, passing EPL variables and literals as parameters and getting return values that can be manipulated.
- You can use common EPL patterns in monitors, spawn to multiple contexts, and manage the lifetime of the monitor instance in EPL.
Design pattern 2: EPL listeners and Java code
In this pattern, most of the listeners and lifetime management happens in EPL.
Consider the following JMon sample which listens for ContextEvent
instances. These instances will contain Context
instances that correspond to real EPL contexts. This Java monitor will enqueue SimpleEvent
instances to these contexts.
JMon application code:
import java.util.List;
import java.util.ArrayList;
import com.apama.jmon.*;
import com.apama.jmon.annotation.*;
@Application(name = "Context Event Sample",
author = "My Name",
version = "1.0",
company = "My Company",
description = "Context Event Java monitor sample",
classpath = ""
)
@MonitorType(description = "A simple Java monitor for demonstrating the use of contexts")
public class ContextEventSample implements Monitor, MatchListener {
/**
* This list will store incoming contexts
*/
List<Context> ctxList;
int ctxCount;
/**
* No argument constructor used by the JMon framework on
* application loading
*/
public ContextEventSample() {
// expecting to receive three contexts
ctxCount = 3;
ctxList = new ArrayList<Context>(ctxCount);
}
/**
* Implementation of the Monitor interface onLoad method. Sets up
* an event expression which looks for all ContextEvents.
*/
public void onLoad() {
EventExpression eventExpr = new EventExpression("all ContextEvent(*): e");
eventExpr.addMatchListener(this);
}
/**
* Implementation of the MatchListener interface match method. Extracts the
* event that caused the event expression to trigger and enqueues an instance
* of SimpleEvent to the retrieved context (which is set up in EPL).
*/
public void match(MatchEvent event) {
ContextEvent ce = ((ContextEvent)event.getMatchingEvents().get("e"));
Context ctx = ce.ctx;
ctxList.add(ctx);
// enqueue a SimpleEvent instance to the retrieved context
(new SimpleEvent("JMon->" + ctx.toString())).enqueueTo(ctx);
if (ctxList.size() == ctxCount) {
// enqueue a SimpleEvent instance to all contexts
(new SimpleEvent("JMon->[all contexts]")).enqueueTo(ctxList);
}
}
}
The above JMon application can be re-written as EPL and a Java plug-in in the following way:
The EPL code imports the plug-in and sets up the listeners. It calls the sendEventsTo
method of the Java plug-in which sends the events to another context using the Correlator
class which allows Java plug-ins to interact with the correlator.
event ContextEvent {
context ctx;
}
monitor ContextEventsSample {
// Load the plug-in
import "ContextEventPlugin" as context_plugin;
action onload() {
// Listen to all ContextEvent events.
on all ContextEvent(*): e {
context_plugin.sendEventTo(e.toString(), e.ctx);
}
}
}
Java plug-in:
import com.apama.epl.plugin.Correlator;
import com.apama.epl.plugin.EventHandler;
/**
Demonstrates a Java plug-in subscribing to channels to receive events
*/ @Application(name = "Context Event Sample",
author = "My Name",
version = "1.0",
company = "My Company",
description = "Context Event Java plug-in sample",
classpath = ""
)
public class ContextPlugin {
public static void sendEventTo(String event, Context context)
{
// Get the current context we're running in
Context current = Context.getCurrent();
// For some reason we don't want to let you do this.
// An exception will terminate the monitor instance
if (current.equals(context))
{
throw new IllegalArgumentException("Please don't use this function to send events to the current context!");
}
// Send the event to the other context
Correlator.sendTo(event, context);
}
}
This design pattern shares the advantages and disadvantages of design pattern 1.
Design pattern 3: EPL stub
In this pattern, the EPL file only contains an EPL onload
stub which invokes the Java plug-in. The Java plug-in uses the Event``Handler
interface for callbacks when it listens for events on the subscribed channel. When an event is received on the channel, the handleEvent
method is invoked.
EPL example (Sample.mon):
event A {
string str;
}
monitor SubscribePluginTest
{
// Load the plug-in
import "SubscribePlugin" as subscribe_plugin;
action onload() {
chunk handler := subscribe_plugin.createHandler("SampleChannel");
send A("Hello World") to "SampleChannel";
}
}
Java plug-in example (SubscribePlugin.java):
import com.apama.epl.plugin.Correlator;
import com.apama.epl.plugin.EventHandler;
/**
Demonstrates a Java plug-in subscribing to channels to receive events
*/
@com.apama.epl.plugin.annotation.EPLPlugin(
name="SubscribePlugin",
description="A sample plug-in"
)
public class SubscribePlugin
{
public static class SubscribeHandler implements EventHandler
{
public void handleEvent(String event, String channel)
{
System.out.println("Got event "+event+" on channel "+channel);
}
}
public static Object createHandler(String channel)
{
SubscribeHandler h = new SubscribeHandler();
Correlator.subscribe(h, channel);
return h;
}
}
Advantages of using this design pattern:
- The listeners and event handling code are all defined in the Java plug-in. So if you are not familiar with EPL, you can use this design pattern.
Disadvantages of using this design pattern:
- Since the listening for events is done in the Java plug-in, there are restrictions on the Apama capabilities that can be used. For example, a Java plug-in can only subscribe to receive everything on a channel. It cannot filter events based on the event types or event expressions, or use event templates.
- You cannot spawn to multiple contexts in a Java plug-in.
For more information on Java plug-ins, see Writing EPL Plug-ins in Java (softwareag.com) in the Apama documentation.
Annotations and utilities for the deployment descriptor file
With the EPL plug-ins written in Java, you can use the following utilities to generate the deployment descriptor file from the annotations in your source files:
- com.apama.jmon.annotation.DirectoryProcessor
- com.apama.jmon.annotation.JarProcessor
You can specify the following annotations in your EPL plug-ins written in Java:
- @Application
- @com.apama.epl.plugin.annotation.EPLPlugin
The following JMon annotations cannot be used in the EPL plug-ins written in Java:
- @EventType
- @MonitorType
- @Wildcard
For more information, see Inserting annotations for deployment descriptor files (softwareag.com) in the Apama documentation.