Skip to content

Developer Guide

This section covers the data model, configuration reference, and extension points available to developers integrating Process Manager into an application.

Data Model

Process Manager persists two entities: a process definition and its execution history. Each process holds a cron schedule, a reference to the Runnable bean that will be executed, and two independent status columns. Each execution record captures the start time, end time, and duration of a single run.

---
config:
  layout: elk
---
erDiagram
    pm_process {
        integer id PK
        varchar name
        varchar schedule_status
        varchar execution_status
        varchar schedule
        varchar job_qualified_name
    }
    pm_execution {
        integer id PK
        date    start_date
        time    start_time
        date    end_date
        time    end_time
        bigint  duration
        integer process_id FK
    }

    pm_process ||--o{ pm_execution : "has"
Hold "Alt" / "Option" to enable pan & zoom

Database Schema

Process Manager manages two tables. The schema below represents the DDL generated by Hibernate for a standard relational database. Column names follow Hibernate's default snake_case naming strategy.

CREATE SEQUENCE pm_process_seq
    START WITH 1
    INCREMENT BY 50;

CREATE SEQUENCE pm_execution_seq
    START WITH 1
    INCREMENT BY 50;

CREATE TABLE pm_process (
    id                 INTEGER      NOT NULL,
    name               VARCHAR(255),
    schedule_status    VARCHAR(255),
    execution_status   VARCHAR(255),
    schedule           VARCHAR(255),
    job_qualified_name VARCHAR(255),
    CONSTRAINT pk_pm_process PRIMARY KEY (id)
);

CREATE TABLE pm_execution (
    id          INTEGER  NOT NULL,
    start_date  DATE,
    start_time  TIME,
    end_date    DATE,
    end_time    TIME,
    duration    BIGINT,
    process_id  INTEGER  NOT NULL,
    CONSTRAINT pk_pm_execution          PRIMARY KEY (id),
    CONSTRAINT fk_pm_execution_process  FOREIGN KEY (process_id) REFERENCES pm_process (id)
);

The schedule_status and execution_status columns store the string representation of the ScheduleStatus and ExecutionStatus enums respectively. The schedule column holds a standard six-field cron expression. The job_qualified_name column stores the fully qualified class name of the Runnable bean assigned to the process.

The start_date, start_time, end_date, and end_time columns of pm_execution are stored in UTC. Conversion to the time zone of the browser happens in the presentation layer.

The sequences are created by Hibernate when GenerationType.AUTO resolves to a sequence-based strategy, which is the default behaviour in Hibernate 6.

Module Overview

Process Manager is structured as six Maven modules following the AppJars layered architecture:

Module Artifact ID Description
Model appjars-process-manager-model DTOs, enums, filters, exceptions, auto-configuration
Business API appjars-process-manager-business ProcessService and ProcessExecutionService interfaces
Business Impl appjars-process-manager-business-impl Service implementations with scheduling logic
Data API appjars-process-manager-data ProcessDao and ProcessExecutionDao interfaces
Data Impl appjars-process-manager-data-impl JPA entities and DAO implementations
Flow UI appjars-process-manager-flow Vaadin views, dialogs, and route configuration

A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.

Spring Auto-Configuration

Process Manager registers itself through Spring Boot's auto-configuration mechanism. The entry point is ProcessManagerAutoConfiguration, declared in:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

This class scans the com.appjars.processmanager package for all Spring components and JPA entities, so no additional @EntityScan or @ComponentScan declarations are required in the host application beyond those described in the Getting Started guide.

On application startup, the service listens to ContextRefreshedEvent to restore scheduled processes. It loads all persisted processes and normalises them:

  • Any process whose Runnable bean can no longer be resolved is transitioned to TASK_NOT_FOUND.
  • Any process left with a null schedule status is defaulted to UNSCHEDULED.
  • Any process still marked RUNNING from a previous run — a state that cannot survive a restart — is reset to READY.
  • A process that fails to normalise is logged and skipped, so a single bad row cannot abort startup.

Processes whose schedule status is ENABLED then have their cron triggers re-registered. On shutdown, a @PreDestroy hook resets stale RUNNING processes and cancels every registered trigger.

Scheduling

Process Manager owns the scheduler it runs jobs on. ProcessManagerSchedulerConfiguration declares a ThreadPoolTaskScheduler under the dedicated bean name processManagerTaskScheduler, built from Spring Boot's ThreadPoolTaskSchedulerBuilder, and ProcessServiceImpl injects it by name.

Two consequences matter for the host application:

  • @EnableScheduling is not required. That annotation only registers the post-processor that drives @Scheduled methods, which this appjar does not use. Owning the scheduler avoids activating scheduling globally as a side effect of adding the appjar.
  • No bean clash. Because the scheduler is injected by name, an application that declares its own TaskScheduler — including one that does use @EnableScheduling for its own @Scheduled methods — does not interfere with Process Manager.

The scheduler honours the standard spring.task.scheduling.* properties, and its threads carry the process-manager-scheduler- name prefix.

Configuration Properties

The following properties can be set in application.properties to customise the behaviour of Process Manager:

Property Default Description
com.appjars.processmanager.url.process pm/process URL path of the process management view
com.appjars.processmanager.executionsdialog.datetimeformat dd/MM/yyyy HH:mm:ss.SS Pattern used for the Start and End columns of the execution history grid
com.appjars.processmanager.executionsdialog.dateformat dd/MM/yyyy Pattern used for the date part of the execution history filters

Process Status Lifecycle

A process carries two orthogonal statuses, persisted in separate columns and modelled by separate enums. The schedule status describes the state of the cron trigger; the execution status describes whether the task is running. Executing a process never overwrites its schedule status, so a process that is running still records the schedule it belongs to.

Schedule status

stateDiagram-v2
    [*] --> PAUSED       : save with schedule
    [*] --> UNSCHEDULED  : save without schedule

    PAUSED       --> ENABLED     : resumeProcess()
    ENABLED      --> PAUSED      : pauseProcess()

    ENABLED      --> UNSCHEDULED : disableProcess()
    PAUSED       --> UNSCHEDULED : disableProcess()
    TASK_NOT_FOUND --> UNSCHEDULED : disableProcess()

    ENABLED      --> TASK_NOT_FOUND : task missing at startup
    PAUSED       --> TASK_NOT_FOUND : task missing at startup
Hold "Alt" / "Option" to enable pan & zoom
Status Description
ENABLED A cron trigger is registered and will fire
PAUSED The schedule is preserved but its trigger is cancelled
UNSCHEDULED No schedule is defined for the process
TASK_NOT_FOUND The Runnable referenced by job_qualified_name no longer resolves

resumeProcess() is valid only from ENABLED and PAUSED; any other status raises InvalidProcessStatusTransition. pauseProcess() is valid only from ENABLED. disableProcess() clears the cron expression and sets UNSCHEDULED regardless of the current status.

Execution status

stateDiagram-v2
    [*]     --> READY
    READY   --> RUNNING : execution starts
    RUNNING --> READY   : execution ends
Hold "Alt" / "Option" to enable pan & zoom
Status Description
RUNNING The process task is executing
READY The process is not executing

runProcess() raises InvalidProcessStatusTransition when the process is already RUNNING. Saving or updating a process resets its execution status to READY.

Real-Time Status Propagation

Both statuses reach the browser without polling. The business layer publishes a Spring application event on every change — ProcessScheduleStatusChangedEvent from ProcessServiceImpl, and ProcessExecutionStatusChangedEvent from ScheduledTaskPersistenceServiceImpl — carrying the process id and the new status. Neither class depends on the UI.

flowchart TD
    A[Status changes in the business layer] --> B[Spring application event]
    B --> C[ProcessStatusSignals]
    C --> D[SharedValueSignal per process]
    D --> E[Badges and row actions in every open ProcessListView]
Hold "Alt" / "Option" to enable pan & zoom

ProcessStatusSignals is a singleton that listens for both events and holds one SharedValueSignal per process and per status kind. ProcessListView binds its badges and its pause, resume, and menu controls to those signals, so a change made by a background scheduler thread or by another administrator repaints every open view. No UI.access() call is involved.

Because propagation happens over server push, the host application must enable it with @Push on its AppShellConfigurator class. Without it, the badges only reflect the state at the moment the view was loaded.

Task Discovery

Process Manager discovers background tasks by scanning the Spring application context for beans that implement java.lang.Runnable. Each discovered bean is wrapped in an AssignableTask and made available for assignment when creating or editing a process.

To register a task, create a @Component that implements Runnable:

@Component
public class ReportGenerationTask implements Runnable {

    @Override
    public void run() {
        // task logic
    }
}

The class name displayed in the UI is derived from the bean's simple class name. The fully qualified class name is persisted in job_qualified_name so the scheduler can reconstruct the task after a restart.

Service API

ProcessService

ProcessService is the primary interface for managing process definitions. It extends CrudService and exposes the following methods specific to the module:

// Retrieve a paginated, filtered, and sorted stream of processes
Stream<ProcessDto> getProcesses(int offset, int limit, ProcessFilter filter, List<ProcessSort> sortOrder);

// Count processes matching a filter
Integer countProcesses(int offset, int limit, ProcessFilter filter);

// Return all Runnable beans available for assignment
Set<AssignableTask> getTasks() throws ClassNotFoundException;

// Register the cron trigger and transition the schedule status to ENABLED
void resumeProcess(ProcessDto process) throws ClassNotFoundException;

// Cancel the trigger and transition the schedule status to PAUSED
void pauseProcess(ProcessDto process);

// Clear the schedule and transition the schedule status to UNSCHEDULED
void disableProcess(ProcessDto process);

// Execute the process once immediately, regardless of its schedule
void runProcess(ProcessDto process) throws ClassNotFoundException;

// Persist a process after a run, without applying the scheduling side effects of update()
void updateOnRun(ProcessDto process);

resumeProcess(), pauseProcess(), and runProcess() throw InvalidProcessStatusTransition when called on a process in a state that does not allow the requested transition. getTasks(), resumeProcess(), and runProcess() throw ClassNotFoundException when the Runnable referenced by the process cannot be resolved in the application context.

ProcessFilter carries id, name, scheduleStatus, and executionStatus; the two status fields filter on the corresponding columns independently.

ScheduledTaskPersistenceService

Persisting the state of a running process is delegated to a separate service so that the transaction that records progress is not rolled back by a failure inside the task itself:

// Persist the process as RUNNING before the task is invoked
void updateProcessBeforeExecution(ProcessDto process);

// Persist the process as READY and store the execution record after the task returns
void saveExecutionAndUpdateProcess(ProcessDto process, ProcessExecutionDto execution);

Both methods publish a ProcessExecutionStatusChangedEvent after committing.

ProcessExecutionService

ProcessExecutionService provides read access to the execution history:

// Retrieve a paginated, filtered, and sorted stream of execution records
Stream<ProcessExecutionDto> getProcessExecutions(int offset, int limit, ProcessExecutionFilter filter, List<ProcessExecutionSort> sortOrder);

// Count execution records matching a filter
Integer countProcessExecutions(int offset, int limit, ProcessExecutionFilter filter);

The bounds carried by ProcessExecutionFilter (startDate, startTime, endDate, endTime) are expressed in UTC, matching the stored columns. Each is optional and applied independently, so a filter may constrain a date without constraining a time.

Execution Flow

Every run — scheduled or triggered manually — is wrapped in a ScheduledTask and submitted to the appjar's own scheduler. It performs the following steps:

  1. The execution status is set to RUNNING and persisted. The schedule status is left untouched.
  2. The start instant is captured in UTC.
  3. The Runnable task's run() method is invoked.
  4. The end instant is captured in UTC and the execution status is set back to READY.
  5. The execution status and a new pm_execution row holding the start, end, and duration in milliseconds are persisted together.

If the task throws, the throwable is caught and logged, the execution record is still saved, and the execution status still returns to READY. A failing task therefore does not affect the scheduling of future runs.

Customisation

Assigning a Router Layout

By default, Process Manager registers its view without a parent layout. To wrap it in the application's main layout, inject the RouteConfigurer bean using its qualifier and call setViewsRouterLayout in a @PostConstruct method:

@Autowired
@Qualifier("ProcessManagerRouteConfigurer")
private RouteConfigurer routeConfigurer;

@PostConstruct
public void configure() {
    routeConfigurer.setViewsRouterLayout(MainLayout.class);
}

The @Qualifier is required because multiple AppJars may contribute a bean named RouteConfigurer.

Customising the View URL

The default URL path can be overridden in application.properties:

com.appjars.processmanager.url.process=myapp/scheduled-tasks

Testing Against the Views

The process views expose stable data-testid attributes so that integration tests can select elements without depending on the DOM structure or on translated labels. The identifiers are declared as constants in com.appjars.processmanager.flow.util.TestIds and cover the grid, the filter fields and buttons, the row action controls, and both dialogs.

Selecting by these attributes is the supported way to drive the views from a test, and keeps tests unaffected by layout or wording changes.