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.
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:
To declare a function in the visual editor, do the following:
Add a new function and define the details:
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. Usevoid
as return type on functions that do not return any value.
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).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:
Define the input parameters. Note that functions can be overloaded.
For every parameter you need to define the following:
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.
To create or edit a text function definition, do the following:
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.
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() }
You can implement your function either in the Expression Language or in Java:
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 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.
If you do not need to inject and use application EJBs into your function, implement your function method as a method of a POJO:
public Decimal average(ExecutionContext ctx)
; the returned context is the parent module context.ctx.getNamespace().setVariableValue("myStringVar", "new value");
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.
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); }
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); } }
Register the EJB:
ComponentServiceBean
in the ejb package. 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); }
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:
public Boolean isIntPrime(Integer i*) native org.whitestein.myapp.customfunctions.PrimeUtils.isPrime;
public class PrimeUtils { public Boolean isPrime(Decimal d) throws ErrorException { int i = d.intValueExact(); return org.apache.commons.math3.primes.Primes.isPrime(i); } }
The weekday is returned as the enumeration literal of the enumeration functionJava::Weekday.
public functionJava::Weekday (Date d*) native com.example.library.customfunctions.DateUtils.getWeekday;
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)); } }
public void getGoalNames() native org.eko.primeapp.customfunctions.GoalServer.getGoalNames;
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); } }
@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; }
@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; } }
@EJB private PdfTools pdfTools; @Override protected void registerCustomComponents() { register(pdfTools, PdfTools.class); }