Applies session state to the connection a client is about to use, and removes it again afterwards.
The case this exists for is a server whose clients do not log in to the database, an authenticator having swapped in a shared database user: the database then sees one user doing everyone's work, and anything depending on who the work is really for - an audit trigger, a row level security policy - has nothing to go on. Setting the relevant session state on the connection is what gives it something.
Discovered via ServiceLoader, so an implementation is registered in
META-INF/services/is.codion.common.db.database.SessionContext, and applied by the Codion server.
All registered instances are applied, shared ones first, and removed in reverse.
public final class AuditContext implements SessionContext {
@Override
public void prepare(ClientInfo clientInfo, Connection connection) throws SQLException {
try (CallableStatement statement = connection.prepareCall("{call set_audit_user(?)}")) {
statement.setString(1, clientInfo.user());
statement.execute();
}
}
@Override
public void release(ClientInfo clientInfo, Connection connection) throws SQLException {
try (CallableStatement statement = connection.prepareCall("{call clear_audit_user()}")) {
statement.execute();
}
}
}
The session is the database's, not Codion's: with a connection pool the connection is a single
database session handed to one client after another, and this is the context swapped in and out around
each use of it. Hence the name, which the databases share - sp_set_session_context on SQL Server,
an application context read through SYS_CONTEXT on Oracle, a session level SET read
through current_setting on PostgreSQL.
This interface lives here, rather than with the server which applies it, so that implementing one costs an application nothing it does not already have: a client carrying a domain model already has this module, whereas the server module brings the whole server with it.
Note the cost of implementing it: with a connection pool this runs on every connection check out, that being the only point at which the connection is known to belong to this client, so whatever it does is paid for by every database call the client makes. State which is the same for every client belongs on the pool, not here.
For the identity of the client alone - who, which application, which host - see
Database.clientInfo(Connection, ClientInfo), which the server applies on its own, more cheaply,
and before any of these. Reach for this interface for what that cannot express.
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionReturns the client type for which to use thisSessionContext.voidprepare(ClientInfo clientInfo, Connection connection) Called once the connection has been made ready for the given client and before it is used: on every check out where the connection comes from a pool, once per connection otherwise.voidrelease(ClientInfo clientInfo, Connection connection) Called before the connection is released, in reverse of the order the contexts were applied in: on every return to the pool where the connection came from one, when the client disconnects otherwise.
-
Method Details
-
clientType
Returns the client type for which to use thisSessionContext. If none is specified, thisSessionContextis shared between all client types.Unlike an authenticator, which selects a single one per client type, any number of contexts may name the same client type and all of them are applied. Shared contexts are applied first, so that a client type specific one sits on top of the general setup and, the removal running in reverse, comes off again before it.
- Returns:
- the String identifying the client type for which to use this context or an empty optional in case this context should be shared
- See Also:
-
prepare
Called once the connection has been made ready for the given client and before it is used: on every check out where the connection comes from a pool, once per connection otherwise.
Throwing aborts the operation the client requested, and any contexts already applied are removed before the exception propagates. That is deliberate, and unlike
Database.clientInfo(Connection, ClientInfo), which is a label and may be skipped: a connection whose session state could not be applied is not the connection the application asked for, and running a query on it may return rows the user should not see.- Parameters:
clientInfo- identifies the client the connection is being prepared forconnection- the connection- Throws:
SQLException- in case the state could not be applied
-
release
Called before the connection is released, in reverse of the order the contexts were applied in: on every return to the pool where the connection came from one, when the client disconnects otherwise.
Always called for a context whose
prepare(ClientInfo, Connection)was called, so that an implementation cannot leave state behind by forgetting a path. Throwing does not abort anything - the client's operation has already finished - but the connection is then considered to be in an unknown state and is discarded rather than handed to the next client.- Parameters:
clientInfo- identifies the client the connection was prepared forconnection- the connection- Throws:
SQLException- in case the state could not be removed
-