Chapter 8. Extension and Plug-in

8.1. Overview

Esper can currently be extended by these means:

8.2. Custom View Implementation

Views in Esper are used to derive information from an event stream, and to represent data windows onto an event stream. This chapter describes how to plug-in a new, custom view.

The following steps are required to develop and use a custom view with Esper.

  1. Implement a view factory class. View factories are classes that accept and check view parameters and instantiate the appropriate view class.

  2. Implement a view class. A view class commonly represents a data window or derives new information from a stream.

  3. Configure the view factory class supplying a view namespace and name in the engine configuration file.

The example view factory and view class that are used in this chapter can be found in the package net.esper.regression.client by the name MyTrendSpotterViewFactory and MyTrendSpotterView.

Views can make use of the following engine services available via StatementServiceContext:

  • The SchedulingService interface allows views to schedule timer callbacks to a view

  • The EventAdapterService interface allows views to create new event types and event instances of a given type.

  • The StatementStopService interface allows view to register a callback that the engine invokes to indicate that the view's statement has been stopped

Note that custom views may use engine services and APIs that can be subject to change between major releases. The engine services discussed above and view APIs are considered part of the engine internal public API and are stable. Any changes to such APIs are disclosed through the release change logs and history. Please also consider contributing your custom view to the Esper project team by submitting the view code through the mailing list or via a JIRA issue.

8.2.1. Implementing a View Factory

A view factory class is responsible for the following functions:

  • Accept zero, one or more view parameters. Validate and parse the parameters as required.

  • Validate that the parameterized view is compatible with its parent view. For example, validate that field names are valid in the event type of the parent view.

  • Instantiate the actual view class.

  • Provide information about the event type of events posted by the view.

View factory classes simply subclass net.esper.view.ViewFactorySupport:

public class MyTrendSpotterViewFactory extends ViewFactorySupport { ...

Your view factory class must implement the setViewParameters method to accept and parse view parameters. The next code snippet shows an implementation of this method. The code obtains a single field name parameter from the parameter list passed to the method:

public class MyTrendSpotterViewFactory extends ViewFactorySupport {
  private String fieldName;
  private EventType eventType;

  public void setViewParameters(ViewFactoryContext viewFactoryContext, 
                          List<Object> viewParameters) throws ViewParameterException
  {
    String errorMessage = "'Trend spotter' view require a single field name as a parameter";
    if (viewParameters.size() != 1) {
      throw new ViewParameterException(errorMessage);
    }

    if (!(viewParameters.get(0) instanceof String)) {
      throw new ViewParameterException(errorMessage);
    }

    fieldName = (String) viewParameters.get(0);
  }
  ...

After the engine supplied view parameters to the factory, the engine will ask the view to attach to its parent view and validate any field name parameters against the parent view's event type. If the view will be generating events of a different type then the events generated by the parent view, then the view factory can create the new event type in this method:

public void attach(EventType parentEventType, 
		StatementServiceContext statementServiceContext, 
		ViewFactory optionalParentFactory, 
		List<ViewFactory> parentViewFactories) 
		    throws ViewAttachException {
  String result = PropertyCheckHelper.checkNumeric(parentEventType, fieldName);
  if (result != null) {
    throw new ViewAttachException(result);
  }

  // create new event type
  Map<String, Class> eventTypeMap = new HashMap<String, Class>();
  eventTypeMap.put(PROPERTY_NAME, Long.class);
  eventType = statementServiceContext.getEventAdapterService().
                         createAnonymousMapType(eventTypeMap);
}

Finally, the engine asks the view factory to create a view instance:

public View makeView(StatementServiceContext statementServiceContext) {
  return new MyTrendSpotterView(statementServiceContext, fieldName);
}

8.2.2. Implementing a View

A view class is responsible for:

  • The setParent method informs the view of the parent view's event type

  • The update method receives insert streams and remove stream events from its parent view

  • The iterator method supplies an (optional) iterator to allow an application to pull or request results from an EPStatement

  • The cloneView method must make a configured copy of the view to enable the view to work in a grouping context together with a std:groupby parent view

View classes simply subclass net.esper.view.ViewSupport:

public class MyTrendSpotterView extends ViewSupport { ...

The view class must implement the setParent(Viewable parent) method. This is an opportunity for the view to initialize and obtain a fast event property getter for later use to obtain event property values. The next code snippet shows an implementation of this method:

public void setParent(Viewable parent) {
  super.setParent(parent);
  if (parent != null)  {
    fieldGetter = parent.getEventType().getGetter(fieldName);
  }
}

Your update method will be processing incoming (insert stream) and outgoing (remove stream) events, as well as providing incoming and outgoing events to child views. The convention required of your update method implementation is that the view releases any insert stream events which the view generates as semantically-equal remove stream events at a later time. A sample update method implementation that computes a number of events in an upward trend is shown below:

public final void update(EventBean[] newData, EventBean[] oldData) {
  EventBean oldDataPost = populateMap(trendcount);

  // add data points
  if (newData != null) {
    for (int i = 0; i < newData.length; i++) {
      double dataPoint = ((Number) fieldGetter.get(newData[i])).doubleValue();

      if (lastDataPoint == null) {
        trendcount = 1L;
      }
      else if (lastDataPoint < dataPoint) {
        trendcount++;
      }
      else if (lastDataPoint > dataPoint) {
        trendcount = 0L;
      }
      lastDataPoint = dataPoint;
    }
  }

  if (this.hasViews())	{
    EventBean newDataPost = populateMap(trendcount);
    updateChildren(new EventBean[] {newDataPost}, new EventBean[] {oldDataPost});
  }
}

This update method adheres to the view convention and posts prior data as an old event. This is a required behavior that views must implement to prevent memory leaks.

Please refer to the sample views for a code sample on how to implement iterator and cloneView methods.

8.2.3. Configuring View Namespace and Name

The view factory class name as well as the view namespace and name for the new view must be added to the engine configuration via the configuration API or using the XML configuration file. The configuration shown below is XML however the same options are available through the configuration API:

<esper-configuration
  <plugin-view namespace="custom" name="trendspotter" 
      factory-class="net.esper.regression.view.MyTrendSpotterViewFactory" /> 
</esper-configuration>

The new view is now ready to use in a statement:

select * from StockTick.custom:trendspotter('price')

Note that the view must implement the copyView method to enable the view to work in a grouping context as shown in the next statement:

select * from StockTick.std:groupby('symbol').custom:trendspotter('price')