Skip to content

Tentackle Session — The Persistence and Security Context Layer

Overview and Motivation

The tentackle-session module defines the session abstraction: the runtime context that ties a user, a connection target, and a transaction together. A Session is the single object every higher layer talks to in order to start transactions, run database work, share state, and be notified about changes — regardless of whether the database is connected directly to the current JVM or reached through a remote Tentackle server (or even a chain of middle-tiers).

The module sits between tentackle-core (its only dependency) and the persistence stack (tentackle-database, tentackle-persistence, tentackle-pdo). It deliberately contains only interfaces, abstract bases, annotations, events, and a handful of small helper classes — the concrete Session implementation lives in tentackle-database (local) and in the remoting modules (remote). This keeps the contract that the whole framework programs against free of any JDBC, SQL-dialect, or wire-protocol detail.

A central design goal is location transparency. The same Session API is used whether persistence runs locally or on a middle-tier server. A session knows whether it is local (the physical database connection lives in this JVM) or remote (calls are routed via TRIP to a server), and application code does not have to care.

Design principles

  • Interface-only contract. Everything that the framework above needs is expressed as an interface (Session, SessionInfo, SessionPool, ModificationTracker, …) located through a factory. Implementations are injected via Tentackle's ServiceFinder/ServiceFactory SPI, so the same calling code binds to a local or remote implementation at runtime.
  • No dependency on the database or SQL layer. Notably, even the connection/driver configuration classes (BackendConfiguration, DriverConfiguration) live here and explicitly avoid depending on tentackle-sql or tentackle-database, so UI and configuration code can manipulate connection settings without dragging in the backend machinery.
  • Location transparency. isRemote()/getRemoteSession() expose locality when needed, but the transaction, property, and change-notification APIs behave identically for local and remote sessions.
  • Thread affinity via thread-locals. The "current" session is held in a ThreadLocal, so domain and persistence code can reach it without threading it through every method signature.

Key Concepts

Session

Session is the heart of the module — a persistence and security context. Its responsibilities group into:

Group Methods Purpose
Current-session management getCurrentSession, setCurrentSession, getSession, makeCurrent, isCurrent, clearCurrent Maintain and reach the thread-local "current" session.
Lifecycle close, reOpen, clone, registerCloseHandler, isPooled Open/close/clone sessions and react to closing.
Transactions begin, commit, rollback, rollbackSilently, transaction(...), isTxRunning, getTxName, getTxNumber, getTxLevel Start and finish (possibly nested) transactions.
Savepoints setSavepoint, rollback(SavepointHandle), releaseSavepoint Fine-grained rollback within a transaction.
Pre/post-commit hooks preCommit, postCommit Run logic immediately before, or right after, the physical commit (e.g. firing events post-commit).
Grouping groupWith, getSessionId, getSessionGroupId Tie several sessions of the same URL into a group that shares e.g. "same user".
Locality isRemote, getRemoteSession, getUrl Discover whether the backend is local or reached via a server.
Keep-alive setAlive, isAlive, setKeepAliveInterval, getKeepAliveInterval Prevent idle remote sessions from being timed out by the server.
State sharing setProperty, getProperty, clearProperties Application state that, for remote sessions, is mirrored to the server-side session.
Misc getDispatcher, setOwnerThread, getInstanceNumber, applyTo(...) Async task dispatch, accidental-cross-thread detection, propagating the session onto SessionDependables.

The static getCurrentSession()/setCurrentSession() pair backs the thread-local model; getSession() asserts one is present and throws a PersistenceException otherwise.

Transactions

begin() returns a transaction voucher (a long). Only the call that actually started the transaction receives a non-zero voucher; nested begin()s return 0 and do nothing. The matching commit(voucher) / rollback(voucher) only commit or roll back if the voucher is the one that opened the transaction — this makes correct nesting trivial: every method may begin()/commit() defensively, and only the outermost actually commits. The transaction(...) convenience methods wrap a lambda in this protocol and are the recommended alternative to the @Transaction interceptor when the holder is not an Interceptable.

TransactionIsolation and TransactionWritability are type-safe enums over the java.sql.Connection isolation constants and read-only/read-write mode, used when a transaction needs something other than the default.

SessionInfo

SessionInfo is the immutable, serializable descriptor of who and what a session represents: user id/name/class-id, password (held in char[], wipeable via clearPassword()), application name/id, locale, timezone, client version, host/OS/VM info, and the connection properties/propertiesName. It travels to the server via TRIP when a remote session logs in, and the server may write values back (e.g., the resolved user-id) for the client to read.

SessionInfos are created by the SessionInfoFactory singleton (default implementation DefaultSessionInfoFactory producing DefaultSessionInfo), which can build one from a username/password, from a properties name, or from an EncryptedProperties object — honouring Cryptor-encrypted credentials. It also carries flags such as lockLingerEnabled (whether token-locks survive a session close) and checkServerVersion for client/server version validation (VersionIncompatibleException).

Session creation, factory, and pooling

  • SessionFactory — the singleton that creates open sessions from a SessionInfo (create(), create(info)), plus a pool-internal create(pool, info). It also manages global close handlers applied to every session.
  • SessionPool — a pool of sessions for one SessionInfo, letting a single user run several sessions in parallel. Built via SessionPoolFactory.
  • MultiUserSessionPool — a pool spanning multiple SessionInfos, so different users can each have parallel sessions (typical for a server).
  • SessionPoolProvider — abstracts whether a pool is local or remote.
  • SessionPooledExecutor — runs Runnables in parallel, each on its own pooled session, for background work.

Modification tracking

The change-notification subsystem lets any JVM learn that data changed, even when the change happened on another client through the server.

  • ModificationTracker — the singleton that tracks global PDO changes. It maintains its own session (and is both a SessionTaskDispatcher and an ExclusiveSessionProvider). Code calls countModification(session, name) when it modifies data, and addDeletion(...) for deletes; listeners are registered via addModificationListener(...). It exposes the current getSerial(class | name), can invalidate() itself, and reports isLocalClientMode() (clients directly on the DB, no middle tier). Convenience statics submit(...) / transaction(...) schedule work on the tracker's own session.
  • ModificationListener — listens for changes by name (usually a table name, but any application name is allowed; null = listen to everything). Beyond dataChanged(event), it controls ordering (getPriority) and batching (getTimeFrame, getTimeDelay) so bursts of events can be coalesced and fired at a randomized time to spread server load. ModificationListenerAdapter is a convenience base.
  • ModificationEvent / ModificationEventDetail — events come in three flavors: a master event (any change, no detail), a single named event, or a multi named event carrying one detail per name.

Master serial events

On top of the bare modification serial, the module supports pushing richer, application-specific events from server to client:

Session task dispatching

Session providers and dependables

  • SessionProvider — anything that can hand out a session.
  • ExclusiveSessionProvider — provides exclusive, reentrant access to a session: once acquired, no other thread gets it until released the matching number of times.
  • SessionDependable — an object carrying a session; Session.applyTo(...) propagates a session onto such objects (and collections of them, including a TrackedList's removed objects), guarding against cycles.
  • SessionHolder / ThreadLocalSessionHolder — a serializable holder of a session; the thread-local variant is handy where no domain context is available.

Connection configuration

These two classes describe how to connect and are persisted through the standard java.util.prefs.Preferences API. Importantly, they depend on neither tentackle-sql nor tentackle-database, so configuration UIs (e.g., in tentackle-fx-rdc) can edit them without the backend on the classpath.

  • AbstractSessionConfiguration — shared base; Validateable and a ScopeConfigurator, with a symbolic name and a persisted flag.
  • BackendConfiguration — the connection target: URL, options, user, password (stored encrypted if a Cryptor is configured) and a reference to a DriverConfiguration. Provides static lookup of all configurations / the default for an application from user or system preferences.
  • DriverConfiguration — how to load the JDBC driver (driver class name + URL to load it from).

Database-centric 2-tier applications, such as BundleMonkey, use this to configure the database connections. It is rarely used in a client-server application.

Annotations describing the persistent model

These annotations let the persistence layer examine the generated model by reflection:

  • @Persistent — marks persistent fields, methods or types, carrying comment, component, parent and an ordinal. The ordinal encodes the field/method order from the model definition.
  • @ClassId — the numeric class id of a persistent object (a MappedService).
  • @TableName — the database table name for a PDO, with build-time options mapSchema (replace dots by underscores) and prefix (configurable via the Wurbelizer plugin's wurbletProperties, e.g. tablePrefix), to retarget schemas/tables without touching the model.

SessionValidationUtilities (a @Service overriding the core ValidationUtilities) uses the @Persistent ordinal so validators fire in priority plus source-code order.

Exceptions

A family of (mostly persistence-related) exceptions is defined here so the upper layers share one vocabulary: PersistenceException (the root), SessionClosedException, LoginFailedException, AlreadyLoggedInException, VersionIncompatibleException, NotFoundException, NotRemovableException and ConstraintException.


Package Layout

Package Contents
org.tentackle.session The whole public API: Session, SessionInfo, the factories and pools, the modification-tracking and master-serial types, the task-dispatcher types, the configuration classes, the model annotations, the Default* helper implementations and the exceptions.
org.tentackle.session.service The Hook (ModuleHook) providing resource-bundle/i18n access for the module.
org.tentackle.session.apt The annotation processor MasterSerialEventServiceAnnotationProcessor for @MasterSerialEventService.

How It Fits Together

Obtaining and using a session

Application code rarely constructs sessions directly; it usually receives one from a pool or the current domain context. At the lowest level, however, the flow is:

// describe who/what
SessionInfo info = SessionInfoFactory.getInstance().create("scott", password, "myapp");

// create an open session (local or remote, decided by the configuration/URL)
Session session = SessionFactory.getInstance().create(info);
session.makeCurrent();                 // becomes the thread-local current session

// transactional work — only the outermost begin() commits
long tx = session.begin("doStuff");
try {
    // ... persistence operations run against Session.getSession() ...
    session.commit(tx);
}
catch (RuntimeException ex) {
    session.rollback(tx);
    throw ex;
}

// or, equivalently, with the lambda form:
session.transaction("doStuff", () -> {
    // ... work ...
    return null;
});

Local vs. remote

The very same code runs unchanged whether session.isRemote() is true or false. For a remote session, the SessionFactory binds a remoting implementation that routes begin/commit/query calls over TRIP to the server, where a mirror session does the actual JDBC work; getRemoteSession() exposes server-side helpers (e.g., server-side logging). For a local session, the implementation in tentackle-database talks to JDBC directly.

Reacting to changes across clients

A client registers a ModificationListener with the ModificationTracker. The tracker (in remote mode) periodically polls the server's master serial; when it advances, the server returns the pending MasterSerialEvents, the matching @MasterSerialEventService handlers run, and registered listeners receive ModificationEvents for the names they care about — so a change committed by one user can refresh the screens of another.


Extending the Module

  • Custom server-to-client events: implement MasterSerialEvent and a Consumer handler annotated with @MasterSerialEventService(YourEvent.class). The server stays oblivious to handlers; clients bind their own.
  • Custom session info: provide a SessionInfo/SessionInfoFactory implementation via the SPI to carry extra per-session attributes between client and server.
  • Reacting to session lifecycle: register a SessionCloseHandler (per session, or globally via the SessionFactory) — but keep it non-blocking, as it runs during close.

Module Dependencies

tentackle-session sits low in the stack and is intentionally thin:

  • It requires transitive tentackle-core (and through it tentackle-common): TRIP serialization, tasks/dispatchers, validation, binding, reflection and service discovery.
  • It opens org.tentackle.session to org.tentackle.core for TRIP, because SessionInfo, modification and master-serial events travel between JVMs.
  • It provides a ModuleHook service (org.tentackle.session.service.Hook) for i18n bundle resolution.
  • It depends on neither tentackle-sql nor tentackle-database; those (and the remoting modules) depend on it and supply the concrete local/remote Session implementations.
  • Tentackle SQL — the database backend layer beneath the local session implementation.
  • PDO / Operations — the persistent domain objects that run on top of sessions.
  • Services / ServiceFinder — the SPI mechanism behind the session, info, and tracker factories.
  • TRIP — the remoting protocol that carries a remote session's calls and its SessionInfo between JVMs.
  • Locking — token locks, whose lockLingerEnabled flag is carried on the SessionInfo.
  • Database — the module providing the concrete local Session implementation.