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'sServiceFinder/ServiceFactorySPI, 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 ontentackle-sqlortentackle-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 aSessionInfo(create(),create(info)), plus a pool-internalcreate(pool, info). It also manages global close handlers applied to every session.SessionPool— a pool of sessions for oneSessionInfo, letting a single user run several sessions in parallel. Built viaSessionPoolFactory.MultiUserSessionPool— a pool spanning multipleSessionInfos, so different users can each have parallel sessions (typical for a server).SessionPoolProvider— abstracts whether a pool is local or remote.SessionPooledExecutor— runsRunnables 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 aSessionTaskDispatcherand anExclusiveSessionProvider). Code callscountModification(session, name)when it modifies data, andaddDeletion(...)for deletes; listeners are registered viaaddModificationListener(...). It exposes the currentgetSerial(class | name), caninvalidate()itself, and reportsisLocalClientMode()(clients directly on the DB, no middle tier). Convenience staticssubmit(...)/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). BeyonddataChanged(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.ModificationListenerAdapteris 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:
MasterSerialwraps thelong serialthe tracker of a remote client periodically polls from the server.MasterSerialEventis an application-defined event (extending the serial with arbitrary payload). Each event class is paired with aConsumerhandler annotated with@MasterSerialEventService— aMappedServicethat maps an event type to its handler. This means the server need not know the handlers at all, and different clients can react differently to the same server-side event.MasterSerialListEventbundles several pending events for a client (always add viaadd(...)so the override-older-events rule is honored).MasterSerialEventHandlerFactory(defaultDefaultMasterSerialEventHandlerFactory) resolves handlers. The annotation is processed at build time byMasterSerialEventServiceAnnotationProcessor.
Session task dispatching¶
SessionTaskDispatcher— aTaskDispatcherbound to aSession, executing submitted tasks serially on one exclusive connection (default implDefaultSessionTaskDispatcher, withSessionTaskDispatcherLock). It can keep its session alive and optionally close it on termination.AbstractSessionTask— base for tasks that need the dispatcher's session (it is aSessionDependable).SessionKeepAliveDaemon/SessionKeepAliveTask— periodicallysetAlive()a session (pinging asynchronously so a slow remote line cannot block the dispatcher), honoring each session'skeepAliveInterval.
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 aTrackedList'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;Validateableand aScopeConfigurator, with a symbolicnameand apersistedflag.BackendConfiguration— the connection target: URL, options, user, password (stored encrypted if aCryptoris configured) and a reference to aDriverConfiguration. 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, carryingcomment,component,parentand anordinal. The ordinal encodes the field/method order from the model definition.@ClassId— the numeric class id of a persistent object (aMappedService).@TableName— the database table name for a PDO, with build-time optionsmapSchema(replace dots by underscores) andprefix(configurable via the Wurbelizer plugin'swurbletProperties, 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
MasterSerialEventand aConsumerhandler annotated with@MasterSerialEventService(YourEvent.class). The server stays oblivious to handlers; clients bind their own. - Custom session info: provide a
SessionInfo/SessionInfoFactoryimplementation 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 theSessionFactory) — 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 ittentackle-common): TRIP serialization, tasks/dispatchers, validation, binding, reflection and service discovery. - It opens
org.tentackle.sessiontoorg.tentackle.corefor TRIP, becauseSessionInfo, modification and master-serial events travel between JVMs. - It provides a
ModuleHookservice (org.tentackle.session.service.Hook) for i18n bundle resolution. - It depends on neither
tentackle-sqlnortentackle-database; those (and the remoting modules) depend on it and supply the concrete local/remoteSessionimplementations.
Related Documentation¶
- 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
SessionInfobetween JVMs. - Locking — token locks, whose
lockLingerEnabledflag is carried on theSessionInfo. - Database — the module providing the concrete local
Sessionimplementation.