Auto-Evaluation

Introduction

The BPM auto-evaluation system intercepts all Axelor framework events (model saves, button clicks, record fetches) to automatically evaluate workflows and inject BPM status into records. This mechanism makes BPM "invisible" to the user — saving a record automatically advances the workflow without requiring explicit user action.

How It Works

The auto-evaluation is powered by the WkfRequestListener, which uses Guice @Observes annotations to intercept three types of framework events:

  1. BeforeTransactionComplete — Model save events

  2. PostAction — Button click events

  3. FETCH — Record fetch events

Each event type triggers a different evaluation behavior.

Model Save Evaluation

When any model is saved (transaction commit):

  1. The onBeforeTransactionComplete() method is triggered

  2. For each updated model, the system checks WkfCache.WKF_MODEL_CACHE to determine if the model is BPM-enabled

  3. If the model is BPM-enabled, wkfInstanceService.evalInstance(model, null) is called with no button signal

  4. This evaluates all active tasks for the associated process instance, checking if any expression conditions are now satisfied

  5. If conditions are met, the corresponding Camunda tasks are completed, automatically advancing the workflow

For deleted models, the system removes the associated WkfInstance record (only when the process config has a single model configuration).

This means that every time a user saves a record that is linked to a BPM process, the workflow is automatically re-evaluated. No explicit "advance workflow" action is needed.

Button Signal Evaluation

When a user clicks a button on a form:

  1. The onRequest() method is triggered (PostAction event)

  2. The button name (_signal) is read from the action context

  3. The system checks two caches:

    • WKF_MODEL_CACHE — Is this model BPM-enabled?

    • WKF_BUTTON_CACHE — Is this button configured as a BPM trigger?

  4. If both checks pass, wkfInstanceService.evalInstance(model, signal) is called with the button name as the signal

  5. The task evaluation engine matches the signal against WkfTaskConfig.button to find which tasks should be triggered

  6. If a task’s button matches but its expression evaluates to false, the task’s helpText is returned as an alert message to the user

  7. A _wkfEvaluated = true flag is set to prevent double evaluation within the same request

This mechanism allows specific form buttons (e.g., "Confirm", "Validate", "Approve") to trigger specific BPM transitions, enabling user-driven workflow advancement alongside the automatic save-based evaluation.

Status Injection on Fetch

When a record is fetched (loaded in the UI):

  1. The onFetch() method is triggered (FETCH request event)

  2. The system calls wkfDisplayService.getWkfStatus() to determine the current BPM status of the record

  3. The $wkfStatus value is injected into the response data

This allows the UI to display the BPM workflow status (e.g., current node name, status color) on any record that has an associated workflow, providing real-time visibility into the process state.

WkfCache

The auto-evaluation system relies on two in-memory caches for performant event interception. Without these caches, every model save and button click would require database queries to determine BPM relevance.

Model Cache

WKF_MODEL_CACHE stores a mapping of process config IDs to model names for all BPM-enabled models. It is structured as Map<String, Map<Long, String>> where:

  • Outer key: tenant ID

  • Inner key: process config ID

  • Inner value: model class name

When a model is saved, the cache provides O(1) lookup to determine if the model’s class name matches any BPM-enabled process configuration.

Button Cache

WKF_BUTTON_CACHE stores a mapping of task config IDs to button names. It is structured as Map<String, MultiMap> where:

  • Key: tenant ID

  • Value: multimap of task config ID to button names

When a button is clicked, the cache provides O(1) lookup to determine if the button name is registered as a BPM trigger.

Cache Lifecycle

Both caches are:

  • Tenant-aware — Each tenant has its own cache entries

  • Initialized on engine startup — Via WkfCache.initWkfModelCache() and WkfCache.initWkfButttonCache() during ProcessEngineServiceImpl construction

  • Lazily re-initialized — If a cache entry is missing at lookup time, it is re-initialized from the database

Evaluation Flow Diagram

The complete auto-evaluation flow when a record is saved:

  1. User saves a record in the UI

  2. Axelor framework fires BeforeTransactionComplete event

  3. WkfRequestListener.onBeforeTransactionComplete() is invoked

  4. Cache lookup: Is this model BPM-enabled?

  5. If yes: WkfInstanceService.evalInstance(model, null) is called

  6. The instance service checks for sub-process relationships

  7. Active Camunda tasks are retrieved for the process instance

  8. For each task, expression conditions are evaluated against the current model state

  9. Tasks whose conditions are met are completed

  10. Recursive evaluation occurs if the process is still active (see Task Evaluation)

Technical Details

Backend Services

  • WkfRequestListener — Observes BeforeTransactionComplete, PostAction, and FETCH events

  • WkfInstanceServiceImpl.evalInstance() — Core evaluation method that triggers task evaluation

  • WkfDisplayService.getWkfStatus() — Computes the BPM status for a record

  • WkfCache — Static in-memory caches for model and button lookups