LSPS documentation logo
LSPS Documentation
Creating a Function

To create a custom function, you will need to

Note: Before you create your own function definition, check the Standard Library for a similar function.

Function Declaration

Functions are declared in function definition files. The files are created as any other definition file, however note that there are two types of these files which differ in the way they are edited:

  • Function definitions edited in the visual function editor that provides graphical support
    functioneditor.png
    Function Editor with a function definition
  • Function definitions edited in the text function editor for more coding-like experience
    functiontextualeditor.png
    Text function editor with a function definition
    Whether the visual or text function Editor is used depends on the type of function definition file: a definition file created for one editor cannot be used by the other. However, you can convert the visual function definition file to the text function definition file from the function definition context menu.

Declaring a Function in the Visual Editor

To declare a function in the visual editor, do the following:

  1. In the Modeling perspective, create or open a function definition file:
    1. Right-click your Module.
    2. Go to New > Function Definition
    3. In the New Function Definition popup:
      • Enter the name of the definition file
      • Make sure the Use text definition format option is unselected.
  2. Add a new function and define the details:

    • Name: name used to call the function
    • Return type: data type of the return value

      Note: Previously, to define a function that returned no value, the user could set the Return type to Null. Since this is the type that is a sub-type of all types, this is discouraged. Use void as return type on functions that do not return any value.

    • Type parameters: comma-separated list of abstractions of data types used in parameters Type parameters allow functions to operate over a parameter that can be of different data types in different calls. The concept is based on generics as used in Java. You can also make the type extend another data type with the extends keyword. The syntax is then <generic_type_1> extends <type1>, <generic_type_2> extends <type2> (for details, refer to the Expression Language User Guide).
    • Public: function visibility
    • Extension method: whether the function can be used as an extension method
    • Variadic: function arity

      A function is variadic if zero or more occurrences of its last parameter are allowed.

    • Has side effects: if true, on validation, the info notification about the function having a side effect is suppressed

      A function is considered to have side effects if one of the following is true:

      • The function modifies a variable outside of the function scope.
      • The function creates a shared record.
      • The function modifies a record field.
      • The function calls a function that causes a side effect.
    • Deprecated: if true, on validation, a notification about that the called function is deprecated is displayed.
    functionDefinition.png
    Example function with type parameters
  3. Define the input parameters. Note that functions can be overloaded.

    For every parameter you need to define the following:

    • Name: parameter name unique within the function declaration
    • Type: data type of the parameter
    • Required: if checked, every function calls must define the parameter. The Required property does not provide any additional runtime check of the parameter value.
    • Default value: if no value for the parameter is passed in the function call, the defined default value is used.
    • Description: optional description of the parameter
  4. Select the implementation type:
    • Java: enter the name to the method with its package

      If you have not implemented the method yet, you can get the Java signature for the function method, for example, public MapHolder createMap(ExecutionContext ctx, Object book, Object genre) throws ErrorException: right-click the function declaration in Outline and select Copy Java Signature to copy it to clipboard. You can then paste it with Ctrl + V.

    • Expression: enter the implementation

Declaring a Function in the Text Editor

To create or edit a text function definition, do the following:

  1. Open the function definition file that you created with the Use text definition format flag or open such a file.

    To convert a visual function definition file to a text function file, right-click the function definition in the GO-BPMN Explorer and click Convert to Text Definition Format.

    Important: The conversion is not reversible.

  2. In the editor, declare the function following the function syntax (for further information on the syntax, refer to the Expression Language Guide).

    Form of function syntax

    <visibility> <return_type> <function_name> (<parameters>){
      <implementation>
    }
    

    Example function declaration and implementation in the Expression Language

    public Integer getArithmeticMean(List<Integer> integersToProcess){
        sum(integersToProcess)/integersToProcess.size()
    }
     
    public Integer getArithmeticMean(Integer... integersToProcess){
        sum(integersToProcess)/integersToProcess.size()
    }
    

Function Implementation

You can implement your function either in the Expression Language or in Java:

Implementing a Function in the Expression Language

When implementing a function in the Expression Language, you enter the implementation into the function definition file along with the function declaration: the syntax depends on whether you are using the text or visual function editor.

  • When in text editor, use the following syntax:
    <visibility> <return_type> <function_name> (<parameters>) <implementation>
  • When in visual editor, enter the expression that returns the required output below the declaration.
functionDefinition.png
Function declaration and definition in the Expression Language

Implementing a Function in Java

When implementing a function as a Java method, you will need to create and deploy a class with the method to the LSPS Server as part of your custom LSPS Application. The implementation in Java can be a POJO or an EJB. The method must be public.

Note that the call to the method from LSPS has the context of the function as its first argument.

Implementing a Function as POJOs

If you do not need to inject and use application EJBs into your function, implement your function method as a method of a POJO:

  1. In the ejb project of your application, create a package with a class that will implement the function:
    1. Define the method signature: you can copy it from the function definition file if you have already declared it: in the function definition file, right-click the function declaration in Outline and select Copy Java Signature.
    2. Implement the logic in a public method of the class.
    3. To access resources of the model instance, such as, variables, signal queue, etc., add the following:
      • ExecutionContext input argument to the method call, for example, public Decimal average(ExecutionContext ctx); the returned context is the parent module context.
      • Work with the data from the execution context (parent Module context) with its respective method, for example, ctx.getNamespace().setVariableValue("myStringVar", "new value");
  2. In the function definition file, define the native statement to call the method:
    public Boolean isIntPrime(Integer i*)
    native org.whitestein.myapp.customfunctions.PrimeUtils.isPrime;
  3. Rebuild and re-deploy the LSPS Application.

Implementing a Function as an EJB

If you need to inject and use application EJBs into your function, implement your function method as a method of an EJB. It is recommended to create the resources in a dedicated package of the <YOUR_APP>-ejb project.

  1. Create the EJB:
    1. Create the interface with the methods which the EJB will implement.

      Example interface for an EJB function

      @Local
      public interface Calculator {
       
        Decimal add(ExecutionContext ctx, Decimal a, Decimal b);
      }
      
    2. Create the EJB and the implementing methods.

      Example stateless bean

      @Stateless
      @PermitAll
      public class CalculatorBean implements Calculator {
       
        @Override
        public Decimal add(ExecutionContext ctx, Decimal a, Decimal b) {
          return a.add(b);
        }
      }
      
  2. Register the EJB:

    1. Create the EJB in the constructor of ComponentServiceBean in the ejb package.
      @EJB
      private <INTERFACE> <BEAN_NAME>;
    2. Call the static register() on the EJB in the registerCustomComponents() method.

    Example EJB registration

      public ComponentServiceBean() {
        super(new ConcurrentHashMap<String, Object>(), new ConcurrentHashMap<Class<?>, List<Object>>());
      }
     
      @EJB
      private Calculator calculatorBean;
     
      @Override
      protected void registerCustomComponents() {
        register(calculatorBean, Calculator.class);
      }
    
  3. Add the function to a function definition file:
    public Decimal addTwoNos(Decimal a, Decimal b)
    native org.eko.primeapp.customfunctions.Calculator.add;
  4. Build and re-deploy the application.

Accessing the Execution Context

When the server creates a model instance, it is created with its model instance data, such as, who and when created the instance, what status it is currently in, etc. and with the contexts of its executable modules, that includes, the parent executable module and any imported modules; all modules are instantiated as module instances of the model instance and start their execution. They create and hold their runtime data and contexts of their process instances; etc.

To inspect the exact structure of a model instance, export a model instance to XML, for example, from the Management perspective of your Designer. Also refer to the modeling-language documentation.

Important: The contexts are by default created on the base execution level; (refer to execution levels).

The execution context of your custom objects is passed to it as the first parameter. To add objects to the context, use the methods of its namespace, for example, to create maps, sets, and lists:

Set<String> names = new HashSet<>();
//populate names ...
//add to the namespace of the context:
myCurrentContext.getNamespace().createSet(names)

Example Functions

POJO function that checks if an Integer is a prime number

  • Declaration:
    public Boolean isIntPrime(Integer i*)
           native org.whitestein.myapp.customfunctions.PrimeUtils.isPrime;
    
  • Implementation:
    public class PrimeUtils {
     
      public Boolean isPrime(Decimal d) throws ErrorException {
        int i = d.intValueExact();
        return org.apache.commons.math3.primes.Primes.isPrime(i);
      }
    }
    

POJO function that returns the day of the week of the received Date

The weekday is returned as the enumeration literal of the enumeration functionJava::Weekday.

  • Declaration:
    public functionJava::Weekday (Date d*)
      native com.example.library.customfunctions.DateUtils.getWeekday;
    
  • Implementation:
    package com.example.library.customfunctions;
     
    import java.util.Calendar;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
     
    import com.whitestein.lsps.lang.exec.EnumerationImpl;
    import com.whitestein.lsps.lang.type.EnumerationType;
     
    public class DateUtils {
     
      private final static Map<Integer, EnumerationImpl> wDays = 
         new HashMap<>();
     
      static {
        EnumerationType weekdayenum = 
            new EnumerationType("functionJava", "Weekday");
     
        wDays.put(Calendar.SUNDAY, new EnumerationImpl(weekdayenum, "Sunday"));
        wDays.put(Calendar.MONDAY, new EnumerationImpl(weekdayenum, "Monday"))
        wDays.put(Calendar.TUESDAY, new EnumerationImpl(weekdayenum, "Tuesday"));
        wDays.put(Calendar.WEDNESDAY, new EnumerationImpl(weekdayenum, "Wednesday"));
        wDays.put(Calendar.THURSDAY, new EnumerationImpl(weekdayenum, "Thursday"));
        wDays.put(Calendar.FRIDAY, new EnumerationImpl(weekdayenum, "Friday"));
        wDays.put(Calendar.SATURDAY, new EnumerationImpl(weekdayenum, "Saturday"));
     
      }
     
      public static EnumerationImpl getWeekday(Date date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
     
        return wDays.get(c.get(Calendar.DAY_OF_WEEK));
      }
    }
    
    javafunction.png
    Custom function definition and declaration

POJO function that returns a Set with names of Goals in a model instance

  • Declaration:
    public void getGoalNames()
           native org.eko.primeapp.customfunctions.GoalServer.getGoalNames;
    
  • Implementation:
    import com.whitestein.lsps.engine.lang.ExecutionContext;
    import com.whitestein.lsps.engine.state.xml.*;
    import com.whitestein.lsps.lang.exec.SetHolder;
     
    public class GoalServer {
     
      public SetHolder getGoalNames(ExecutionContext ctx) {
     
        Set<String> names = new HashSet();
     
        for (ProcessInstance processInstance : ctx.getModelInstance().getProcessInstances()) {
          for (Goal goalValue : processInstance.getGoals()) {
            for (GOElement goalChild : goalValue.getChildren()) {
              //populate a set of strings   
              names.add(goalChild.getName());
     
            }
          }
        }
        return ctx.getNamespace().createSet(names);
      }
    }
    

Stateless-EJB function

  • Interface:
    @Local
    public interface PdfTools {
     
      /**
       * Check if the file is valid PDF file.
       *
       * @param context
       * @param binaryPdf
       * @return is provided file of type PDF, true or false
       */
      boolean isValidPdf(ExecutionContext context, BinaryHolder binaryPdf);
     
      /**
       * Extract a creation date from PFD file.
       * 
       * @param context
       * @param binaryPdf
       * @return Date when provided PDF was created or null if no or invalid value.
       * @throws IOException if file is not valid PDF file
       */
      Date getCreationDate(ExecutionContext context, BinaryHolder binaryPdf) throws IOException;
     
      /**
       * Read document title from provided PDF file.
       * 
       * @param context
       * @param binaryPdf
       * @return Title of the provided PDF or null if no value.
       * @throws IOException if file is not valid PDF file
       */
      String getTitle(ExecutionContext context, BinaryHolder binaryPdf) throws IOException;
    }
    
  • Implemenation:
    @Stateless
    @PermitAll
    @Interceptors({ LspsFunctionInterceptor.class })
    public class PdfToolsImpl implements PdfTools {
     
      @Override
      public boolean isValidPdf(ExecutionContext context, BinaryHolder binaryPdf) {
        try {
          PdfReader reader = new PdfReader(binaryPdf.getData());
          reader.close();
          return true;
        } catch (IOException e) {
          //Throws an exception when file is not PDF file
          return false;
        }
      }
     
      @Override
      public Date getCreationDate(ExecutionContext context, BinaryHolder binaryPdf) throws IOException {
        PdfReader reader = new PdfReader(binaryPdf.getData());
        Map<?, ?> info = reader.getInfo();
        String pdfDateString = (String) info.get("CreationDate"); //PdfName.CREATIONDATE contains invalid value in this version
        reader.close();
        if (pdfDateString == null) { // no value in the PDF
          return null;
        }
        Calendar creationDateCalendar = PdfDate.decode(pdfDateString);
        if (creationDateCalendar == null) { // invalid value in the PDF
          return null;
        }
        return creationDateCalendar.getTime();
      }
     
      @Override
      public String getTitle(ExecutionContext context, BinaryHolder binaryPdf) throws IOException {
        PdfReader reader = new PdfReader(binaryPdf.getData());
        Map<?, ?> info = reader.getInfo();
        String pdfTitle = (String) info.get("Title");
        reader.close();
        return pdfTitle;
      }
    }
    
  • Registration
    @EJB
    private PdfTools pdfTools;
     
    @Override
    protected void registerCustomComponents() {
       register(pdfTools, PdfTools.class);
    }