The following is intended as a reference for implementing plugin event handlers to provide custom logic in response to events being fired at defined points throughout the execution cycle in both the Web Determinations Interview Engine and Web Determinations platform.
This section describes the specific engine and platform events - providing details about the objects contained by the events (accessible and modifiable by associated event handler implementations) and the mapping between the events and the event handler interfaces to be implemented by plugin classes in order to be registered as valid event handlers for the respective events.
These events are tied to an instance of the InterviewEngine. They allow subscribers to be notified when a rulebase has been added, removed or otherwise modified. This is particularly useful when using a Rulebase Service Plugin that is capable of dynamically reloading rulebases (also known as, hotswapping).
EVENT |
ENCAPSULATED OBJECTS |
DESCRIPTION |
EVENT HANDLER INTERFACE |
---|---|---|---|
OnRulebaseAddedEvent | InterviewRulebase object that has just been added. | Fired immediately after the RulebaseService has notified the InterviewEngine that a rulebase has been added. | OnRulebaseAddedEventHandler |
OnRulebaseRemovedEvent | InterviewRulebase object that has just been removed. | Fired immediately after the RulebaseService has notified the InterviewEngine that a rulebase has been removed. | OnRulebaseRemovedEventHandler |
OnRulebaseUpdatedEvent | InterviewRulebase object that has just been updated. | Fired immediately after the RulebaseService has notified the InterviewEngine that a rulebase has been updated. | OnRulebaseUpdatedEventHandler |
These events are tied to an instance of the InterviewSession:
EVENT |
ENCAPSULATED OBJECTS |
DESCRIPTION |
EVENT HANDLER INTERFACE |
---|---|---|---|
OnSessionCreatedEvent |
|
Fired immediately after an interactive session is created, with the InteractiveEngine object as the sender. This is the chance to inject data into the session or perform any other custom initialization logic required.
|
OnSessionCreatedEventHandler |
BeforeSessionDestroyedEvent |
|
Fired just before a Web Determinations session is to be destroyed, with the InterviewSession as the sender. This is the last chance to extract the session data out of the session and persist it somewhere, clean up any resources created/opened during the session's lifetime, and so on. |
BeforeSessionDestroyedEventHandler |
OnValidateControlEvent |
|
Fired immediately after an input control is validated by the core validation logic, with the Web Determinations Engine ControlInstance as the sender. Custom validation may be applied here and errors and/or warnings attached to the transaction result object. |
OnValidateControlEventHandler |
OnValidateScreenEvent |
|
Fired immediately after an Interview screen is validated by the core validation logic, with the Web Determinations Engine InterviewScreen object as the sender. Custom validation may be applied here and errors and/or warnings attached to the transaction result object. | OnValidateScreenEventHandler |
BeforeSubmitDataEvent |
|
Fired immediately before data (encapsulated in an InterviewUserData object instance) is to be submitted into an Web Determinations session, with the InterviewSession object as the sender. Event handler implementations that handle this event have the ability to cancel the submission by appending an error to the transaction result object. |
BeforeSubmitDataEventHandler |
OnSubmitCommitEvent |
|
Fired immediately after data submitted into an Web Determinations session is committed to the rulebase session, with the InterviewSession object as the sender. If this event fires, it means that the data submitted has been attached to the underlying rule session and is safe to work with, persist, export, and so on. Some sort of auto-save functionality could be implemented as a handler of this event. |
OnSubmitCommitEventHandler |
OnSubmitRollbackEvent |
|
Fired immediately after a failed submit data operation was rolled back, with the InterviewSession object as the sender. If this event fires, it means that the data submitted has not been committed to the underlying rule session |
OnSubmitRollbackEventHandler |
OnSubmitDataEvent |
|
Fired immediately after the submit operation, regardless of whether the operation was successful and the data submitted was committed or the operation failed and the operation was rolled back. Note that this event will not fire if the transaction result contained errors immediately after the call to handleEvent on all the BeforeSubmitDataEventHandler implementations - the submission operation is aborted in this case and the BeforeSubmitDataEvent is the only submission event fired. |
OnSubmitDataEventHandler |
OnGenerateDocumentEvent |
|
Fired when the user submits a document generation action. The event handler receives the document type and other various document-generation parameters in the event object. The event handler also has access to the InterviewSession object from the sender input argument. The event handler has the option of generating a document of its own and providing it to the event object (as a 'replacement document'). The WD Server will use the replacement document and bypasses normal Document Generation steps. | OnGenerateDocumentEventHandler |
These events are tied to an instance of the Web Determinations WebDeterminationsServlet. They are designed to provide access to the native HTTP Request, Response, and Session objects. Unlike other events the encapsulated objects differ with regards to the .NET and Java implementations.
EVENT |
ENCAPSULATED OBJECTS |
DESCRIPTION |
EVENT HANDLER INTERFACE |
---|---|---|---|
OnRequestEvent |
Java
|
Fired on receipt of either a HTTP Get or Post request, immediately prior the request being processed by Web Determinations. | OnRequestEventHandler |
.NET
|
|||
OnWriteResponseEvent |
Java
|
Fired after Web Determinations has processed the request but prior to any response being written to the response stream. | OnWriteResponseEventHandler |
.NET
|
These events are tied to an instance of the Web Determinations SessionContext:
EVENT |
ENCAPSULATED OBJECTS |
DESCRIPTION |
EVENT HANDLER INTERFACE |
---|---|---|---|
OnInvestigationStartedEvent |
|
Fired immediately after an investigation on a particular goal attribute is started. |
OnInvestigationStartedEventHandler |
OnInvestigationEndedEvent |
|
Fired immediately after an investigation on a particular goal attribute is concluded (ie. the goal becomes known). |
OnInvestigationEndedEventHandler |
OnGetScreenEvent |
|
Fired after the Platform Screen is generated from the InterviewScreen, and before it is rendered to HTML. The Screen object can be a Native or Custom screen. The sender object is the SessionContext object for the current session. |
OnGetScreenEventHandler |
OnRenderScreenEvent |
|
Fired after the HTML is generated from the Platform Screen object, and before the HTML is set back as a Response. The sender object is the SessionContext object for the current session. |
OnRenderScreenEventHandler |
OnSaveEvent |
|
Fired when the user clicks on the 'Save' button to save the current interview data, and before the DataAdaptor 'save' is called. The handler can provide a Case ID for the save - poor man's data adaptor, and a quick way to autogenerate CaseID for save (avoiding the 'Get CaseID from user' screen'). The handler can also raise an error and halt the save process. |
OnSaveEventHandler |
OnRequireSessionEvent |
|
Fired if the current Interview request requires an InterviewSession, and it is null. The handler can provide the InterviewSession to be used by the Interview request. The handler can use the URI object, which contains various details about the current request such as the rulebase, locale, request action, and so on. |
OnRequestSessionEventHandler |
Along with the requirements placed upon Plugin implementations, event handler implementations must conform to the following:
As an example event handler implementation, we take the case of an event handler that implements the BeforeDataSubmitEventHandler interface. That is, it handles events of the BeforeSubmitDataEvent type. The purpose of this event handler is to abort all Web Determinations session data submit operations. Not very useful, but it will do as an example case. The implementation of this plugin is given below.
DataSubmissionAborter.java
package com.haley.interactive.engine.events.handlers;
\\
import com.haley.interactive.engine.data.error.GenericError;
import com.haley.interactive.engine.events.BeforeSubmitDataEvent;
import com.haley.util.plugins.Plugin;
import com.haley.util.plugins.RegisterArgs;
import com.haley.util.plugins.events.Event;
\\
/**
* Aborts the submission process by appending errors to the transaction result object encapsulated by the
* passed event object.
*
* @author fhmeidan
*
*/
public class DataSubmissionAborter implements BeforeSubmitDataEventHandler {
\\
/**
* Add an error to the transaction result object, aborting the submission process.
*/
public void handleEvent(Event event, Object sender) {
BeforeSubmitDataEvent submitEvent = (BeforeSubmitDataEvent) event;
submitEvent.getResult().addError(new GenericError("Generic testing error added to cancel submission"));
}
\\
/**
* Not picky about when an instance of this plugin is available.
*/
public Plugin getInstance(RegisterArgs args) {
return new DataSubmissionAborter();
}
\\
/**
* Parameterless constructor to conform to the plugin requirements
*/
public DataSubmissionAborter(){
\\
}
\\
}
This example takes the case of an event handler that is intended to only be used on a particular rulebase - the 'SpecialRulebase' rulebase. This following class demonstrates how an event handler implementation (which implements both OnSessionCreatedEventHandler and BeforeSessionDestroyedEventHandler) may be implemented to handle OnSessionCreatedEvent and BeforeSessionDestroyedEvent events only for sessions created for investigating goals of the 'SpecialRulebase' rulebase. In this case, we print a message to console out when a session on this rulebase is created and before one is about to be destroyed.
RulebaseSpecificHandler.java
package com.haley.interactive.engine.events.handlers;
\\
import com.haley.interactive.engine.data.error.GenericError;
\\
\\
import com.haley.interactive.engine.events.BeforeSubmitDataEvent;
import com.haley.util.plugins.Plugin;
import com.haley.util.plugins.RegisterArgs;
import com.haley.util.plugins.events.Event;
\\
/**
* Logs a message to console out when an interactive session is started on the 'SpecialRulebase' rulebase.
*
*
* @author fhmeidan
*
*/
public class RulebaseSpecificHandler implements OnSessionCreatedEventHandler, BeforeSessionDestroyedEventHandler {
\\
/**
* Check the type of event and output a message accordingly.
*/
public void handleEvent(Event event, Object sender) {
if (event instanceof BeforeSessionDestroyedEvent){
System.out.println("Session " + ((BeforeSessionDestroyedEvent)event).getSession().toString() + " about to be destroyed.");
} else if (event instanceof OnSessionCreatedEvent) {
System.out.println("Session " + ((OnSessionCreatedEvent)event).getSession().toString() + " has been created.");
}
}
\\
/**
* Only provide this plugin if the interactive session's rulebase is the 'SpecialRulebase' rulebase.
* The specific RegisterArgs object we get here is an instance of InterviewSessionRegisterArgs.
*/
public Plugin getInstance(RegisterArgs args) {
InterviewSessionRegisterArgs sessionArgs = (InterviewSessionRegisterArgs) args;
if (sessionArgs.getSession().getRulebase().getIdentifier().equals("SpecialRulebase")){
return new RulebaseSpecificHandler();
} else {
return null;
}
}
\\
/**
* Parameterless constructor to conform to the plugin requirements
*/
public RulebaseSpecificHandler(){
\\
}
\\
}
This example demonstrates how to perform custom validation to inputs from controls in a screen, by creating an Event Handler for OnValidateControlEvent.
The Event OnValidateControlEvent is only fired once the core validation in Web Determinations has been performed. Core validation are the validation options provided to rulebase authors during screen authoring such as 'mandatory'. By implementing a handler for OnValidateControlEvent, the developer can add his/her own validation to user values from a specified input control.
The code below demonstrates the following:
CustomValidator.java
package com.oracle.determinations.interview.engine.userplugins.events;
\\
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
\\
import com.oracle.determinations.engine.Attribute;
import com.oracle.determinations.engine.HaleyType;
import com.oracle.determinations.interview.engine.data.TransactionResult;
import com.oracle.determinations.interview.engine.data.error.AttributeValueError;
import com.oracle.determinations.interview.engine.data.error.Error;
import com.oracle.determinations.interview.engine.data.error.GenericError;
import com.oracle.determinations.interview.engine.events.OnValidateControlEvent;
import com.oracle.determinations.interview.engine.events.handlers.OnValidateControlEventHandler;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionPlugin;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionRegisterArgs;
import com.oracle.determinations.interview.engine.screen.InputInterviewControl;
import com.oracle.determinations.interview.engine.screen.InterviewScreen;
import com.sun.org.apache.xerces.internal.impl.dtd.models.DFAContentModel;
\\
public class CustomValidator implements OnValidateControlEventHandler {
\\
public CustomValidator() {
\\
}
\\
public void handleEvent(Object sender, OnValidateControlEvent event) {
//Setup target
InputInterviewControl targetControl = event.getControl();
TransactionResult validationTransaction = event.getResult();
\\
//For validation that requires the rulebase model data of the control - i.e. the Attribute
Attribute controlAttribute = targetControl.getAttribute();
\\
//For validation with the instance data of the control - i.e. the answer the user provided and submitted
byte controlValueType = targetControl.getValueType();
Object controlValue = targetControl.getValue();
\\
//There are many other InputInterviewControl members that are useful for custom validation, e.g.
/*
targetControl.getId()
targetControl.getProperties()
targetControl.isReadOnly()
targetControl.getScreen()
*/
\\
//Simple validation - validate 'Age' input
//Purpose: to demonstrate how to perform validation on input values, and raising an error for validation failure
if(controlAttribute.getName().toLowerCase().equals("age") && targetControl.getValueType() == HaleyType.NUMBER)
{
Long longAge = (Long)controlValue;
int intAge = longAge.intValue();
\\
if(intAge > 130 || intAge < 1)
{
validationTransaction.addError(new GenericError("Age value must be greater than 0 and less than 130"));
}
}
\\
//More complex validation - validate 'Australian' postcode.
//For this example, the list of valid postcode values have been downloaded from Australia Post and stored in our database.
//Purpose: to demonstrate validation of an input control together with the value of another input control in the SAME screen, and validating using datasources
if((controlAttribute.getName().toLowerCase().equals("postcode") || controlAttribute.getName().toLowerCase().equals("zipcode")) &&
targetControl.getValueType() == HaleyType.NUMBER)
{
//Check that the selected country is Australia before processing the postcode as an Australian postcode
\\
//Get Country control by getting it from the current control's screen. We know they are on the same screen because the rule author created a screen
//with both country and postcode input in the same screen
InterviewScreen controlScreen = targetControl.getScreen();
List<InputInterviewControl> screenControls = controlScreen.getControls();
InputInterviewControl countryControl = null;
for(InputInterviewControl tempControl : screenControls)
{
if(tempControl.getAttribute().getName().toLowerCase().equals("country"))
{
countryControl = tempControl;
}
}
\\
String country = (String)countryControl.getValue();
if(country.toLowerCase().equals("australia"))
{
Long longPostcode = (Long)controlValue;
String strPostcode = longPostcode.toString();
\\
//Check digit count first, then actual value validity
Pattern postcodePattern = Pattern.compile("[0-9]{4}");
Matcher possibleMatch = postcodePattern.matcher(strPostcode);
if(!possibleMatch.matches())
{
validationTransaction.addError(new GenericError("Australian postcode values must be 4 digits"));
}
else
{
//Access database to check if value exists in Postcode table - e.g.
//Statement s
//..
//s.execute("select from POSTCODES where postcode='" + strPostcode + "'");
\\
\\
//Raise error if not found in Postcode table - e.g.
//validationTransaction.addError(new GenericError("The postcode provided is not a valid Australian postcode"));
}
}
}
\\
}
\\
public InterviewSessionPlugin getInstance(InterviewSessionRegisterArgs args) {
return new CustomValidator();
}
\\
}
This example demonstrates how to perform screen-wide validation to input values in a screen, by creating an Event Handler for OnValidateScreenEvent.
The OnValidateScreenEvent is called after validation to all the controls in the screen has been performed - both core validation and custom validations, therefore validation errors/warnings/events from individual control validations will be present in the TransactionResult. By implementing a handler for OnValidateScreenEvent, the developer can perform screen-wide validations - validation that uses a combination of input values from various controls. The validations can also leverage on whether there was any errors/warnings/events added from individual control validations into the TransactionResult.
The code below demonstrates the following:
ScreenValidation.java
package com.oracle.determinations.interview.engine.userplugins.events;
\\
import java.util.List;
import java.util.Map;
\\
import com.oracle.determinations.interview.engine.data.TransactionResult;
import com.oracle.determinations.interview.engine.data.error.GenericError;
import com.oracle.determinations.interview.engine.data.error.Warning;
import com.oracle.determinations.interview.engine.events.OnValidateScreenEvent;
import com.oracle.determinations.interview.engine.events.handlers.OnValidateScreenEventHandler;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionPlugin;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionRegisterArgs;
import com.oracle.determinations.interview.engine.screen.InputInterviewControl;
import com.oracle.determinations.interview.engine.screen.InterviewControl;
import com.oracle.determinations.interview.engine.screen.InterviewScreen;
\\
public class ScreenValidation implements OnValidateScreenEventHandler {
\\
public ScreenValidation()
{
\\
}
\\
public void handleEvent(Object sender, OnValidateScreenEvent event) {
//Setup target
InterviewScreen targetScreen = event.getScreenInstance();
TransactionResult validationTransaction = event.getResult();
\\
//Handy members of InterviewScreen
InterviewControl anInterviewControl = targetScreen.findControl("id");|
List<InterviewControl> screenControls = targetScreen.getControls();
String screenName = targetScreen.getName();
Map properties = targetScreen.getProperties();
boolean isAutomatic = targetScreen.isAutomatic();
\\
//Further validation can be performed here for input values in the controls,
// e.g. screen-wide validations that uses values from several controls in the target screen
\\
//Screen-wide validation - validate the address and phone details the user has provided, and only validate if individual control validations were successful
if(screenName.toLowerCase().equals("User Address and Phone Details") && validationTransaction.isSuccess())
{
InputInterviewControl streetControl = (InputInterviewControl)targetScreen.findControl("street");
InputInterviewControl suburbControl = (InputInterviewControl)targetScreen.findControl("suburb");
InputInterviewControl stateControl = (InputInterviewControl)targetScreen.findControl("state");
InputInterviewControl postcodeControl = (InputInterviewControl)targetScreen.findControl("postcode");
InputInterviewControl phoneControl = (InputInterviewControl)targetScreen.findControl("phone");
\\
//Build address as one String
String fullAddress = (String)streetControl.getValue() + ", " +
(String)suburbControl.getValue() + " " + (String)stateControl.getValue() + " " + (String)postcodeControl.getValue();
\\
\\
boolean addressValid = false;
//Assuming there is access to an Address verifier (to validate if the address is valid)
//call API validation here, and set addressValid to true/false
if(!addressValid)
{
//add error to Transaction if validation fails e.g.
validationTransaction.addError(new GenericError("Address detail provided is invalid"));
}
else
{
boolean addressToPhoneValid = false;
//Assuming there is access to an Address-to-Phone verifier (to verify if the phone is registered to the address provided
//call API validation for Address-to-Phone here, and set addressToPhoneValid to true/false
if(!addressToPhoneValid)
{
validationTransaction.addError(new GenericError("The phone number provide does not belong to the Address detail provided"));
}
}
\\
}
\\
}
\\
public InterviewSessionPlugin getInstance(InterviewSessionRegisterArgs args) {
return new ScreenValidation();
}
\\
}
This example demonstrates an Event handler that implements two Event Handler interface, and thus can handle two types of fired Events. It implements the OnGetScreenEventHandler and OnInvestigationEndedEventHandler.
The sample code below is an Event Handler that performs 'autosaving'. It calls the Data Adaptor Plugin of the current Web Determinations Interview to save the interview data when:
AutoSaveTrigger.java
package com.oracle.determinations.interview.engine.userplugins.autosave;
\\
import com.oracle.determinations.interview.engine.InterviewSession;
import com.oracle.determinations.interview.engine.SecurityToken;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionPlugin;
import com.oracle.determinations.interview.engine.plugins.InterviewSessionRegisterArgs;
import com.oracle.determinations.interview.engine.screen.InterviewScreen;
import com.oracle.determinations.interview.engine.security.BasicSecurityToken;
import com.oracle.determinations.web.platform.controller.SessionContext;
import com.oracle.determinations.web.platform.datamodel.screen.NativeScreen;
import com.oracle.determinations.web.platform.datamodel.screen.Screen;
import com.oracle.determinations.web.platform.eventmodel.events.OnGetScreenEvent;
import com.oracle.determinations.web.platform.eventmodel.events.OnInvestigationEndedEvent;
import com.oracle.determinations.web.platform.eventmodel.handlers.OnGetScreenEventHandler;
import com.oracle.determinations.web.platform.eventmodel.handlers.OnInvestigationEndedEventHandler;
import com.oracle.determinations.web.platform.plugins.PlatformSessionPlugin;
import com.oracle.determinations.web.platform.plugins.PlatformSessionRegisterArgs;
\\
public class AutoSaveTrigger implements OnGetScreenEventHandler, OnInvestigationEndedEventHandler {
\\
public AutoSaveTrigger()
{
//parameter-less constructor to satisfy Plugin requirement
}
\\
//Triggers autosaving during the investigation of a goal
public void handleEvent(Object sender, OnGetScreenEvent event) {
SessionContext currentContext = (SessionContext) sender;
\\
SecurityToken token = new BasicSecurityToken("");
InterviewSession currentSession = currentContext.getInteractiveSession();
String caseID = currentContext.getCaseID();
\\
//This calls the DataAdaptor save() during the investigation of a goal, autosaving on each investigation screen
//BUT it does not autosave when the investigation is completed - thus not saving the user's answer to the last Question
if(currentContext.getCurrentGoal() != null)
{
saveData(currentContext, token, currentSession);
}
}
\\
//Triggers autosaving when an investigation of a goal finishes
public void handleEvent(Object sender, OnInvestigationEndedEvent event) {
SessionContext currentContext = event.getSessionContext();
\\
SecurityToken token = new BasicSecurityToken("");
InterviewSession currentSession = currentContext.getInteractiveSession();
String caseID = currentContext.getCaseID();
\\
//This calls the DataAdaptor save() when the investigation finishes, thus saving the user's answer to the last Question
if(caseID != null)
{
saveData(currentContext, token, currentSession);
}
\\
}
\\
private void saveData(SessionContext currentContext, SecurityToken token,
InterviewSession currentSession) {
String newCaseID = currentSession.getDataAdaptor().save(token, currentContext.getCaseID(), currentSession);
if(newCaseID != "")
currentContext.setCaseID(newCaseID);
}
\\
public PlatformSessionPlugin getInstance(PlatformSessionRegisterArgs args) {
//Demonstration of a Plugin only registering if the current rulebase is 'ExtFrameworkAutoSave'
if (args.getContext().getInteractiveSession().getRulebase().getIdentifier().equals("ExtFrameworkAutoSave")){
return new AutoSaveTrigger();
} else {
return null;
}
}
\\
}