Skip to content

tentackle-fx-rdc — Rich Desktop Client

Overview and Motivation

RDC stands for Rich Desktop Client. The tentackle-fx-rdc module is the glue that turns the two lower layers it sits on — the PDO layer and the extended JavaFX layer — into a ready-to-run desktop application.

Where tentackle-fx knows everything about JavaFX controls, controllers and binding but nothing about persistence, and tentackle-pdo knows everything about persistent domain objects but nothing about a UI, tentackle-fx-rdc is the only module that knows about both. It answers the questions that arise as soon as a PDO needs to be shown to and edited by a user:

  • How is a PDO searched for, displayed, edited and saved through a UI?
  • How does a PDO render itself in a table, a tree or a combo box?
  • What happens when the user right-clicks a PDO — which context menu appears?
  • How does the whole application start up, log in against a backend and shut down again?

The module is deliberately built around a small number of extension points so that an application provides only the parts that are specific to its entities, while the framework supplies the generic CRUD machinery, dialogs, background execution and lifecycle.

Design principles

  • One coordination object per entity. Everything the UI needs to know about a PDO type is bundled behind a single interface — the GuiProvider. Applications implement (or partially override) it per entity; the framework discovers the implementation via the SPI mechanism.
  • Generic CRUD, specific views. The search-, edit- and tree machinery (PdoSearch, PdoCrud, PdoController, …) is entity-agnostic. The only entity-specific artifacts an application must supply are the editor view, the finder view and the table/tree configuration.
  • Never block the FX thread. Persistence operations may go to a local database or across the network. RDC funnels them through a background pool and marshals the results back onto the FX application thread (see Background execution).
  • Location transparency, inherited. Because PDOs are remoting-capable, the same UI code works whether the client talks to the database directly or through a middle-tier server — no LazyInitializationException, ever.
  • Convention with escape hatches. Defaults (DefaultGuiProvider, DefaultPdoFinder, DefaultPdoEditor, DefaultRdcFactory) cover the common case; every one of them can be replaced or subclassed.

Where it sits

tentackle-fx-rdc   ── knows PDOs *and* JavaFX ─────────────┐
        │ requires transitive                              │
        ├── org.tentackle.fx     (controls, controllers,   │  the only module
        │                         binding, tables)         │  that bridges the
        └── org.tentackle.pdo    (persistent domain        │  UI and the
                                  objects, sessions, DDD)   │  persistence world

The companion modules build on top of it:

Module Purpose
tentackle-fx-rdc-poi Excel (Apache POI) export of RDC tables.
tentackle-fx-rdc-update Application self-update / deployment support.
tentackle-test-fx-rdc JUnit 5 / TestNG base classes for testing GuiProviders, controllers and table configurations.

Key Concepts

GuiProvider — the per-entity hub

The GuiProvider<T> is the central abstraction of the module. One GuiProvider exists per PDO type and encapsulates all the UI behavior for that entity. Its responsibilities fall into a few groups:

  • Identity / appearancecreateGraphic() for an icon, the resource bundle (getBundle(), isBundleProvided()).
  • EditingisEditorAvailable(), isEditAllowed(), isViewAllowed(), createEditor() returning a PdoEditor<T>.
  • SearchingisFinderAvailable(), createFinder() returning a PdoFinder<T>.
  • Drag & dropcreateDragboard(...), isDragAccepted(...), dropDragboard(...).
  • Tree renderinggetTreeRoot(), getTreeText(...), getToolTipText(...), createTreeItem(), getTreeCellFactory(), and the navigation hooks getTreeChildObjects(...) / getTreeParentObjects(...) with their providesTreeChildObjects() / getTreeExpandMaxDepth() guards.
  • Table renderingcreateTableView().

Applications rarely implement the whole interface. They subclass DefaultGuiProvider and override only what differs. A provider is registered declaratively:

@GuiProviderService(Invoice.class)
public class InvoiceGuiProvider extends DefaultGuiProvider<Invoice> {

  @Override
  public PdoEditor<Invoice> createEditor() {
    return Fx.load(InvoiceEditor.class);     // an FXML controller
  }

  @Override
  public PdoFinder<Invoice> createFinder() {
    return Fx.load(InvoiceFinder.class);
  }
}

The @GuiProviderService annotation is picked up at build time by an annotation processor (see Annotation processors) which generates the META-INF/services entry, so the provider is found by GuiProviderFactory at runtime — in both modular and non-modular deployments.

Read-only views with PdoViewer

When an entity can be looked at but not edited, wrap a plain view controller in a PdoViewer and disable editing:

@Override
public PdoEditor<Incident> createEditor() {
  return new PdoViewer<Incident, IncidentView>(Fx.load(IncidentView.class));
}

@Override
public boolean isEditAllowed() {
  return false;     // grays out the "edit" context-menu item
}

Rdc and RdcFactory — the entry points

Rdc is a static convenience facade (mirroring the role Fx plays in tentackle-fx) that bundles the most common operations so calling code reads naturally. The heavy lifting is delegated to the SPI-resolved RdcFactory (default: DefaultRdcFactory).

Typical one-liners:

// open a modal edit dialog for a PDO
Rdc.displayCrudStage(invoice, /*editable*/ true, ownerWindow);

// open a search dialog for an entity
Rdc.displaySearchStage(on(Invoice.class), Modality.NONE, ownerWindow);

// render a PDO in a tree
Rdc.showTree(owner, rootPdo);

// build cells / tree items for a table or tree
TreeItem<Invoice> item = Rdc.createTreeItem(invoice);

Rdc also exposes the standard save / discard / cancel dialog (showSaveDiscardCancelDialog), stage-hierarchy helpers (closeStageHierarchy) and the background-execution helpers described below.

CRUD controllers: PdoController, PdoCrud, PdoSearch

The generic editing machinery is layered:

Type Role
PdoController<T> Base FX controller bound to a single PDO (setPdo).
PdoEditor<T> A controller that edits a PDO; supplied by the GuiProvider.
PdoFinder<T> Holds the search-criteria view and runs the query.
PdoCrud<T> The full create/read/update/delete controller wrapping an editor with toolbar, navigation and persistence actions.
PdoSearch<T> The search controller wrapping a finder with a result table that opens CRUD on selection.

The default finder, DefaultPdoFinder, provides a normtext search field when the entity defines a normtext, and otherwise selects all PDOs immediately — so even an entity with no custom finder view is searchable out of the box.

Tables, trees and cells

RDC plugs PDOs into the table and tree infrastructure of tentackle-fx:

Value translators

The translate package supplies ready-made value translators for PDOs, so a PersistentDomainObject can appear in any FX control without boilerplate:

Context menus

Right-clicking a PDO in a table or tree raises a context menu assembled by a PdoContextMenuFactory (default DefaultPdoContextMenuFactory). Menu items are themselves discoverable services, so an application can add its own without touching the framework:

  • Items implement PdoTableContextMenuItem, PdoTreeContextMenuItem or PdoTreeTableContextMenuItem and are declared with the matching @PdoTableContextMenuItemService / @PdoTreeContextMenuItemService / @PdoTreeTableContextMenuItemService annotation.
  • The built-in items live in the contextmenu package: EditItem, ViewItem, ExpandItem and CollapseItem.

Events

PdoEvent, together with EventListenerProxy, notifies the UI about persistence operations (save, delete, …) on a PDO so that open views, tables and trees can react and refresh.

Background execution

Persistence calls must never run on the JavaFX application thread. RDC provides the bg(...) helpers on Rdc (backed by RdcUtilitiesWithBackgroundPool) to run work off-thread and post the result back:

Rdc.bg(node,
       () -> invoice.reload(),            // runs in background pool
       reloaded -> refreshView(reloaded), // runs on the FX thread
       ex -> showError(ex));              // FX thread, on failure

This pattern keeps the UI responsive regardless of whether the PDO is served locally or remotely.


Application lifecycle (app, login)

The app and login packages turn the building blocks above into a complete, launchable application.

  • FxApplication — the base class for any Tentackle FX application.
  • LoginApplication — usually the application that is launched first. It shows the login view and spawns the login handler; on success it hands over to the real application.
  • DesktopApplication<C> — the main desktop application, parameterized by its main controller type.
  • LoginFailedHandler and LogoutEventHandler — pluggable handlers for the corresponding lifecycle events.

The login package implements the login flow: Login (the controller), LoginController (the interface that lets an application substitute its own login view) and Backends (add / edit / remove the server backends the user can connect to).

A minimal startup therefore looks like:

launch LoginApplication
   └─ user picks a Backend and authenticates  (login package)
        └─ on success → DesktopApplication starts with the main controller
              └─ controllers use Rdc / GuiProvider to search, view and edit PDOs

Security and administration


Annotation processors (apt)

To keep service discovery working in both modular (JPMS) and classpath deployments, the apt package provides validators to ensure the contracts.

  • GuiProviderServiceAnnotationProcessor — for @GuiProviderService.
  • PdoTableContextMenuItemServiceAnnotationProcessor / PdoTreeContextMenuItemServiceAnnotationProcessor — for the context-menu item service annotations.

How It Fits Together

A typical "open an entity for editing" flow ties the pieces together:

  1. The user triggers an action (menu, button, double-click in a table).
  2. Calling code asks Rdc for the operation, e.g. Rdc.displayCrudStage(pdo, true, owner).
  3. RdcFactory resolves the GuiProvider for the PDO's type via GuiProviderFactory.
  4. The provider creates a PdoEditor (an FX controller from tentackle-fx), which PdoCrud wraps with toolbar, navigation and persistence actions.
  5. The editor's controls are bound to the PDO's attributes; edits flow through the binding layer.
  6. On save, PdoCrud runs pdo.save() via Rdc.bg(...) off the FX thread; on completion a PdoEvent refreshes any other open views.

The application only had to supply an editor view (or even use a generated default) and a GuiProvider; everything else came from the framework.


Package Reference

Package Contents
org.tentackle.fx.rdc Core: Rdc, RdcFactory, GuiProvider, CRUD controllers, table/tree cells, events, utilities.
org.tentackle.fx.rdc.app Application lifecycle: FxApplication, LoginApplication, DesktopApplication, lifecycle handlers.
org.tentackle.fx.rdc.login Login view, controller and backend management.
org.tentackle.fx.rdc.crud The generic PdoCrud create/read/update/delete controller.
org.tentackle.fx.rdc.search PdoSearch and the default finder.
org.tentackle.fx.rdc.table RDC table configuration, column popup, printing, table utilities.
org.tentackle.fx.rdc.component(.delegate/.build) PDO-aware combo/choice boxes with their delegates and builders.
org.tentackle.fx.rdc.translate Predefined PDO value translators.
org.tentackle.fx.rdc.contextmenu Built-in context-menu items (edit, view, expand, collapse).
org.tentackle.fx.rdc.security Security-rules editing UI.
org.tentackle.fx.rdc.admin Administrative views (e.g. live sessions).
org.tentackle.fx.rdc.apt Annotation processors for service registration.
org.tentackle.fx.rdc.service Module hook / runtime registration.

See also