FPL Custom Functions

Documentation home

 

Overview. 1

Creating a Custom Function. 2

Custom Function Registration. 2

Creating the Java Program. 2

Programming notes 4

Testing the Java Program outside of Ebase. 5

Extending FPL Language Syntax 6

To add a Function to the FPL.jj file: 7

Generating the UfsLanguage.jar file. 8

Replacing the UFSLanguage.jar File. 8

Adding your Java functions to the Ebase server CLASSPATH. 8

 

See also:       FPL Script Command Syntax, Ebase External Interface

 

Overview

If you are considering writing an FPL custom function, you might also consider an alternative approach of using an API based scripting language (e.g. Javascript) to access Java classes directly. You can achieve the same results using Javascript and with much less effort. Click here for details.

 

The Ebase form processing language (FPL) can be extended with user custom functions. Once a custom function is made available to Ebase it can be used within any processing script. As with system provided functions e.g. uppercase, lowercase etc, custom functions can be nested with other functions, can appear in any expression, and may take any Ebase fields or other functions as parameters.

 

e.g. if repayment(loan, years, interestRate, mortgageType) is defined as a custom function then valid usage within FPL would include

 

set field1 = repayment(field1, field2, 5.0, field3);

if [repayment(field1, field2, 5.0, field3) > Field5] then ...

 

set Premium = repayment(field1, field2, 5.0, round(field3)) + F6;

 

 

Each custom function is mapped to a Java class. Whenever the Ebase form processor comes across a custom function during form script execution it will automatically call the mapped Java class and pass any defined parameters. Ebase achieves this by instantiating the class which must be written to a specific specification (see below) and executing the run() method on that class. The run() method contains the programming logic to execute the custom function.

 

Please note that at runtime, custom function classes are instantiated once, then used many times for different forms and different users. For this reason, they should not contain instance level variables. See programming notes.

 

Custom functions enable data from any application that exists outside of the Ebase environment to be incorporated into form processing logic. Equally, they allow data to be written directly from an Ebase form to any external application. Custom functions are called from FPL scripts which themselves are executed via the Event Model implemented by Ebase.

 

A custom function might be used to:

 

 

Custom functions are limited in that they can only return a single value. Custom resources should be used to communicate with external systems or interfaces where more complex data structures are needed.

 

Creating a Custom Function

 

Creating a custom function requires a number of discreet steps:

 

  1. Register the custom function to Ebase.
  2. Create the Java class that the custom function is mapped to.
  3. Add the custom function syntax to the FPL language definition.
  4. Add the compiled class plus any other classes required to the Ebase CLASSPATH.

 

Custom Function Registration

 

Custom functions are registered on the server using the Server Administration Application. This is required before the function can be executed.

On the designer registration is optional: if a function is registered it will appear in the list of available functions shown with the code assist feature (Ctrl space) and the function name will be shown in blue in the editor. The simplest way to add a function to the designer is to register it first on the server, then copy the userFunctions.xml file from the server to the designer. This file is located:

 

 

Creating the Java Program

 

The Java program must extend UFSCustomFunction as shown in the example below:

 

/**

* Class : Repayment

*

* Function : Provides run time mapping to the Ebase repayment custom function.

*

*/

 

import java.util.*;

import org.nfunk.jep.*;

import com.ebasetech.ufs.function.*;

import com.ebasetech.ufs.kernel.*;

 

public class Repayment extends UFSCustomFunction {

/**

* Constructor

*/

public Repayment(UFSFormInterface formInterface) {

 

      super(formInterface);

 

/* specify the number of parameters to be received by this custom function */

     

      numberOfParameters = 4;

}

/**

* Function : Custom function to perform a mortgage repayment recalculation.

*

* Parameters : loan : amount of loan

* intRate : interest rate

* years : repayment period

* repaymentType : I for Interest Only, R for Repayment

*

*/

public void run(Stack inStack) throws FormException, ParseException {

 

      // check the stack

      //

      checkStack(inStack);

 

      // get the parameter from the stack

      //

      Object param1 = inStack.pop();

      Object param2 = inStack.pop();

      Object param3 = inStack.pop();

      Object param4 = inStack.pop();

 

      // check whether the argument is of the right type

      //

      if ( param1 instanceof String

            && param2 instanceof Double

            && param3 instanceof Double

            && param4 instanceof Double) {

 

            // set up the passed parameters...

            //

            double loan = ((Double) param4).doubleValue();

            double intRate = ((Double) param3).doubleValue();

            double years = ((Double) param2).doubleValue();

            String repaymentType = param1.toString();

 

            // perform the function...

            //

            double rate = intRate / 100;

 

            double top = loan * rate;

            double bot;

 

            if (repaymentType.equals("I")) {

                  bot = 1;

            } else {

                  bot = 1 - 1 / Math.pow(rate + 1, years);

            }

 

            double amt = (top / bot) / 12;

            amt = Math.round(amt * 100) / 100.0;

           

            // push the result on the inStack

            //

            inStack.push(new Double(amt));

           

      } else {

            throw new ParseException("Invalid parameter type");

      }

}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

°

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Notes:

 

1.      The class must extend the UFSCustomFunction class.

 

2.      The number of parameters to be received by the function must be set in the constructor. Please do not place any additional code in the class constructor. See programming notes.

 

3.      Instance level variables must not be used. See programming notes.

 

4.      Parameters are always received via a Stack object.

 

5.      Once parameters are retrieved from the stack their type must be checked. If the type does not match the expected type then an exception must be thrown.

 

6.      The result of the function is returned via the Stack.

 

At execution time i.e. when FPL finds a custom function, the Ebase form processor will instantiate the Class mapped to the custom function and will execute the run() method of that Class.

 

Programming notes

1.      The form external interface class is made available to the function class. This can be used to directly access and change form attributes such as current page, field values etc. (See Ebase External Interface for more information) This can be used by referring to attribute formInterface e.g.

 

String currentPage = formInterface.getCurrentPage();

 

2.      Function classes are shared between users and must not contain any instance level variables.

For example, do not do this:

 

public class Repayment extends UFSCustomFunction

{

  private Object var1;       // this is an instance level variable

  public void run(Stack inStack) throws FormException, ParseException

  {

var1 = createObject();

..

 

Instead, create new objects only inside methods:

 

public class Repayment extends UFSCustomFunction

{

  public void run(Stack inStack) throws FormException, ParseException

  {

Object var1 = createObject();        // this is a method level variable

..

 

 

3.      Functions should signal errors by throwing either a ParseException or a FormException. ParseException should be thrown for command syntax errors and FormException for other runtime problems. Both of these will result in termination of the form and logging of the error. In addition, if the system is in test mode, the error message will be displayed on the HTML page and the execution log. e.g.

 

if ( errorCondition )

{

throw new FormException("Description of this particular error");

}    

 

4.      Functions wishing to return an integer, say int myInt, should return a Double representing the value. e.g.

 

inStack.push(new Double(myInt));

 

5.      Functions wishing to return a boolean, should return a Double containing the value 0.0 for false and 1.0 for true.

 

 

Testing the Java Program outside of Ebase

 

The following Java is an example of a test program for the Repayment function above. It enables the Java Class which implements the custom function to be tested before integration with Ebase.

 

/**

* Class : RepaymentTest

*

* Function : Tests the repayment custom function.

*

*/

 

import org.nfunk.jep.*;

import com.ebasetech.ufs.kernel.*;

class RepaymentTest {

      /**

       * Constructor.

       */

      public RepaymentTest() {}

/**

* Main method. Test of the repayment custom function.

*/

public static void main(String args[])throws FormException{

 

      double value;

 

      // create a new parser...

      //

      JEP parser = new JEP();

 

      // define an expression to test the repayment function...

      //

      String expr = "repayment(10000,25,5,\"R\")";

 

      // add the function class to the expression evaluator...

      //

      parser.addFunction("repayment",

          new com.ebasetech.ufs.customfunctions.Repayment(null));

 

      // now parse the expression...

      //

      parser.parseExpression(expr);

      if (parser.hasError()) {

            System.out.println("Error while parsing");

            System.out.println(parser.getErrorInfo());

            return;

      }

 

      // now retrieve the result from evaluating repayment function...

      //

      value = parser.getValue();

      if (parser.hasError()) {

            System.out.println("Error during evaluation");

            System.out.println(parser.getErrorInfo());

            return;

      }

      System.out.println(expr + " = " + parser.getValue());

}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

­

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Extending FPL Language Syntax

 

In order to use a custom function within FPL the definition of the function must be added to FPL Script Command Syntax. This is necessary because FPL syntax must be accurately checked when creating scripts within the Ebase Designer. Language syntax checking is invoked when the verify button is clicked within the script editor.

 

Ebase stores the FPL language definition in Extended Bachus Naur Form (EBNF) and uses JAVACC, a Java compiler compiler from Sun Microsystems, to generate language syntax checking software (Java).

 

The process of adding a new function to FPL is as follows:

 

 

The FPL language definition is stored in file FPL.jj, which is included in the LanguageBuilder/Fpl directory of the Ebase distribution. The FPL.jj file is a text document.

 

To add a Function to the FPL.jj file:

 

1. Open the FPL.jj in a text editor.

 

2. Add the name of the function to the section below

 

/***********************************************

* Custom function token definitions added below

***********************************************

*/

{

< STARTSWITH: "STARTSWITH">

| < ENDSWITH: "ENDSWITH">

| < CONTAINS: "CONTAINS">

| < UPPERCASE: "UPPERCASE">

| < LOWERCASE: "LOWERCASE">

| < SUBSTRING: "SUBSTRING">

| < ROUND: "ROUND">

| < TOSTRING: "TOSTRING">

| < DAYOFWEEK: "DAYOFWEEK">

| < DATETOSTRING: "DATETOSTRING">

| < REPAYMENT: "REPAYMENT">

}

 

3. Also add the function to the following section

 

/********************************************************

* Add custom function to the Function() definition below

********************************************************

*/

void Function() :

{}

{ ( StartsWith() | EndsWith() | Contains() | Uppercase() | Lowercase() | Substring() | Round() |

Tostring() | DayofWeek() | DateToString() |

Repayment()

)

}

 

4. Lastly, add the function definition to the following section

 

/****************************************

* Custom function definitions added below

****************************************

*/

void Repayment() :

{}

{

<REPAYMENT> <LPAREN> Expression() <COMMA> Expression() <COMMA> Expression() <COMMA> Expression() <RPAREN>

}

 

The repayment function definition is shown as an example. The example represents a function called repayment, which accepts four comma-delimited parameters, each of which can be an expression. An expression can be any combination of literals, constants, field names, operators and other functions.

 

If your function has syntactical requirements beyond this example please contact Ebase Technology Technical Support for assistance.

 

Once the FPL.jj file has been amended, the language syntax checking software must be generated.

 

Generating the UfsLanguage.jar file

Double click on file ufsLanguageBuilder.jar in directory LanguageBuilder of the Ebase distribution to invoke the language builder application as shown below:

 

 

Navigate to the amended FPL.jj, specify the location for the target jar file, then click Build. This will build a UFSLanguage.jar file to the loacation you have specified.

 

Replacing the UFSLanguage.jar File

The UFSLanguage.jar file is required by the designer only. It's location will vary according to the installation. Typically, it will be in folder .../UfsClient/lib. Copy the new file to the designer.

 

Adding your Java functions to the Ebase server CLASSPATH

On the server, add jar files to directory .../ufs/WEB-INF/lib and class files to directory .../ufs/WEB-INF/classes. Jar files and classes added to these directories are automatically added to the Ebase web application classpath. If classes are added, the directory structure beneath the classes directory must match the full package name of the Java class exactly, respecting case. e.g. class abc.xyz.MyClass.class would result in the structure /WEB-INF/classes/abc/xyz/MyClass.class.