LSPS documentation logo
LSPS Documentation
Working with a Model

At some point you will want to work with data of model instances from your application. When manipulating a model instance, make sure to take into consideration:

Execution Levels

An execution context can exist on different execution levels so one context element can have different values in context versions on different levels.

This mechanism serves to separate possibly transient data from the "real" data: the real data exist in the contexts on the base level or 0 level, the execution level where model instances are created. Consequently, for example, changes on shared records in contexts on this level are reflected instantly in the database.

From a custom object, you can create contexts on sublevels: on the sublevel context you work with copies of the contexts and their data. Once happy with the changes, you can merge your changes into its super contexts. Sublevels are designated with an additional digit added to the original level digit separated by a colon, for example 0:1, 0:1:1, 0:1:2 etc.

Note: The GUI mechanism provided by the ui module makes use of execution-level mechanism:

  • Each form is created on the so-called screen level
  • Contexts of View Models are created on sublevels referred to as evaluation levels.

Restrictions

  • A shared record instance created in a non-base level and not merged into the base level is not registered in the entity manager and hence not returned by queries.
  • Functions with side effects can cause changes in application state even if evaluated in a non-base evaluation level. Such functions are, for example, createModelInstance() and sendSignal().

Creating an Execution Level

To create an execution level, call com.whitestein.lsps.engine.state.xml.EvaluationLevelUtils.nextSublevel(String level, ModelInstance modelInstance).

Note that the level does not contain any data when created: the data of non-base levels are loaded when you request an entity that is not present on that level. The system attempts to load it from the immediate parent context and, if not available, it continues to request higher context levels until it reaches the base level. It is when you change the context data that a context on a non-base level stores data.

Merging an Execution Level

To merge all changes from a non-base context into the context on the parent level, use the com.whitestein.lsps.engine.lang.EvaluationLevelMerger.mergeLevel(String level).

On merge, the system checks for data conflicts. For example, if a variable is changed in two contexts on the second level (the one above the base level) and both context are merged to the base-level context, a conflict is detected. In the case of records, the conflict check is performed on each property: If the property P1 of a record R is changed in one context and property P2 of the same record R is changed in another context no conflict is detected during merge.

Cleaning an Execution Level

To clean changes in a level, call one of the com.whitestein.lsps.engine.state.xml.EvaluationLevelUtils methods:

  • cleanDataOfEvaluationLevel(ModelInstance modelInstance, String level)
  • cleanDataOfLevelAndSublevels(ModelInstance, String).

Note that the cleanDataOfLevelAndSublevels(ModelInstance, String) method cleans changes in the given level and in all child levels.

  • To remove a context from a level, use one of the methods:
    • removeDataOfEvaluationLevel(ModelInstance, String level)
    • removeDataFromLevelAndSublevels(ModelInstance, String level): removes all entities that belong to the execution level and its sub-levels

Checking for Changes on an Execution Level

To check if there are changes in the non-base levels, call com.whitestein.lsps.engine.state.xml.ModelInstance.isDirty().

Creating a Record

To create a record instance in the execution context of your custom object, use the createRecord() method of the namespace.

//custom form component implementation (the class extends com.whitestein.lsps.vaadin.forms.FormComponent):
RecordHolder colorHolder = getNamespace().createRecord("forms::Color");
//custom task and function:
factory.createRecord(
  "GoogleCalendar::GoogleCalendarEvent",
  new HashMap<String, Object>() {
    { put("title", event.getTitle().getPlainText());
      put("content", event.getTextContent().getContent().getPlainText());
      put("date", new Date(event.getTimes().get(0).getStartTime().getValue()));
    }
  }
);

Generating Java Classes and Interfaces for Data Types

To work more efficiently with elements of your data type model and record instances in the LSPS Application, generate the Java wrapper classes and interfaces for the types and use these classes rather than using the RecordHolder class to work with Records.

Example of difference in coding

// original access via context:
RecordHolder record = ctx.getNamespace().createRecord("core::ConstraintViolation");
record.setProperty("message", msg);
 
// better with generated Java class for records:
ConstraintViolationRecord better = new ConstraintViolationRecord(record);
better.setMessage(msg);

To generate Java classes and interfaces for records, interfaces and enumerations, do the following:

  1. On the data-type element, define the properties of its class and interface to be generated:
    1. Select the record, interface, or enumeration.
    2. Open the Java tab in the Properties view:
      • To generate a class, select the Generate Java class option. Optionally, just the output class name and the constant name with the path to the record.
      • To generate an interface, select the Generate Java interface.
        javaClassForRecord.png
  2. Open the Properties view of individual record fields and on the Java tab, select the methods you want to include in the generated class and interface. Note that methods definitions of Records are ignored: only getters and setters of record fields are generated as per this configuration.
    javaMethodsForRecordField.png
  3. Save the changes.
  4. Generate the classes and interfaces:
    1. Right-click a module or project with the records and go to Generate > Record Java Sources.
    2. In the dialog, define the properties:
    3. Select the modules, for which to generate the Java sources.
    4. For each selected module, define the properties for the classes and interfaces:
      • Source folder: target src folder in a Java project
      • Package: target package name
      • Class name prefix: prefix of exported class names
      • Class name suffix: suffix of exported class names
      • Class extends superclass: superclass of the generated record classes
      • Class implements interface: interface of the generated record classes
      • Interface extends interface: common interface extended by the generated interfaces
      • Additional record types: other records that should be included in the operation

        The parameter is primarily intended for inclusion of library records.

        generatingJavaRecordHolders.png
        Generating Java record wrappers
        generatingJavaRecordHoldersForLib.png
        Generating Java record wrappers for a library
  5. Refresh the project with the target src directory.
generatedJavaRecordHolders.png
Generated Java record wrappers
Implementation of a custom function and its definition

customFunctionWithRecordHolder.png
Custom Function implementation and definition

Mapping Enumerations to Existing Java Classes

If you have existing Java implementations of your Enumerations, you can map them to the Enumerations in your module to prevent them from being generated when you generate the Java classes of your records:

  1. Select the Enumeration.
  2. Open the Java tab in the Properties view.
  3. Select Map to Java class name and enter the class name of the implementation.
enumerationWithMappingToJava.png

Checking a Record Constraint

To work with constraints on a record, use the following methods:

  • getConstraints returns a collection of all constraints that are applied to a given record type and properties.These can be potentially null.
    executionContext.getProcessModel().getConstraints(recordA, propertyA)
  • findTag returns a validation tag with the given qualified name (may be simple or * full).
    executionContext.getProcessModel().findTag(qid)

Throwing a Signal

A Signal is a special object used for communication within a model instance or with another model instance.

You can produce signals in your custom object using the server API. To catch signals, use the Signal Start Events or Catch Signal Intermediate Events.

Note: You can also use signal modeling elements to work with signals in your models and signal-related standard-library functions, such as sendSignal().

You can work with signals as follows:

  • To send a synchronous signal to the parent model instance of the custom object, use the addSignal() call of the object's context.

    Sending a signal to the current model instance from a custom task type

      //start method of a custom task
      @Override
      public Result start(TaskContext context) throws ErrorException {
        Decimal d = (Decimal) context.getParameter("number");
        Integer i = d.intValueExact();
        //adding the signal to the task type context:
        context.addSignal(
           "prime check output:"
           + System.out.println(org.apache.commons.math3.primes.Primes.isPrime(i))
        );
        return Result.FINISHED;
      }
    
  • To send a signal to another model instance:
    1. Define and register your object as an EJB.
    2. Inject CommunicationService into your bean object.
    3. Send a signal with the sendSync() or sendAsynch() method of the CommunicationService bean.

      Example sendSync() call from the start method of a custom task type

        @EJB
        private CommunicationService communicationService;
        ...
        @Override
        public Result start(TaskContext context) throws ErrorException {
          long modelInstanceId = 71000;
          String signal = "signal";
          SignalMessage signalMessage = new SignalMessage(
              //sender of the signal:
              Identifier.ofModelInstance(context.getModelInstance().getId()),
              //receiver of the signal:
              Identifier.ofModelInstance(71000), new ObjectValue(signal));
          try {
            //sending the signal:
            communicationService.sendSync(signalMessage);
          } catch (ModelInstanceNotFoundException | InvalidModelInstanceStateException e) {
            e.printStackTrace();
          }
          return Result.FINISHED;
        }
      

Throwing an Error

You can throw errors directly from your custom objects.

To catch error, use the Error Start Events or Error Intermediate Events.

To throw an error, do the following:

  • In implementation in the Expression Language, use the error(<error_code>) function.
    errorInFunctionInEL.png
  • In implementation in Java, throw com.whitestein.lsps.common.ErrorException.
    errorInFunctionInJava.png

Creating Hooks on Model Execution

To perform an action always when an instance of a model is created, started, or finished, do the following:

  1. Create a stateless bean that implements the ModelInstancePlugin interface.
        public class MyModelInstancePlugin implements ModelInstancePlugin {
            
          @Override
          public void onCreate(CreateCommand cmd, ModelInstanceEntity modelInstance) {
            System.out.println(">>>>>>>>>>>>>>>>>> This is on create hook <<<<<<<<<<<<<<<<");
          }
        ...
    
  2. Register the bean with the ComponentServiceBean:
          @EJB(beanName = "MyModelInstancePlugin")
          private ModelInstancePlugin myMIPlugin;
            
          @Override
          protected void registerCustomComponents() {
            register(myMIPlugin, ModelInstancePlugin.class);
          }
    

Invoking the Command Line

To use the command-line tool from a Java program, do the following:

  1. Include the dependency to the command line Management Console in your pom.xml:
    <dependency>
        <groupId>com.whitestein.lsps.mconsolecl</groupId>
        <artifactId>lsps-mconsole-cl</artifactId>
        <version>${project.version}</version>
    </dependency>
    
  2. Call the main method with the cli command as its String[] argument.
    com.whitestein.lsps.mconsolecl.Main.main(new String[]{"arg 1", "arg 2"});

For details about the console are available in the Management documentation.