All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pushtechnology.diffusion.client.session.Session Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2014, 2024 DiffusionData Ltd., All Rights Reserved.
 *
 * Use is subject to licence terms.
 *
 * NOTICE: All information contained herein is, and remains the
 * property of DiffusionData. The intellectual and technical
 * concepts contained herein are proprietary to DiffusionData and
 * may be covered by U.S. and Foreign Patents, patents in process, and
 * are protected by trade secret or copyright law.
 *******************************************************************************/
package com.pushtechnology.diffusion.client.session;

import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.Messaging;
import com.pushtechnology.diffusion.client.features.Security;
import com.pushtechnology.diffusion.client.features.Topics;
import com.pushtechnology.diffusion.client.features.control.clients.ClientControl;
import com.pushtechnology.diffusion.client.types.Credentials;
import com.pushtechnology.diffusion.client.types.PathPermission;

/**
 * A client session to a server or cluster of servers.
 * 

* A new session can be created by connecting to a server using * {@link SessionFactory#open(String)}, specifying the server URL. There is also * a non-blocking variant {@link SessionFactory#openAsync(String)}. The session * factory can be configured to control the behavior the session. *

* The session provides a variety of operations to the application. These are * grouped into feature interfaces, such as {@link Topics} and * {@link Messaging}, exposed to the application through the * {@link #feature(Class)} method. * *

Session lifecycle

*

* Each session is managed by a server. The server assigns the session a * {@link #getSessionId() unique identity}, and manages the session's topic * subscriptions, security details, and session * properties. * *

* A session can be terminated using {@link #close()}. A session may also be * terminated by the server because of an error or a time out, or by other * privileged sessions using the {@link ClientControl} feature. * *

* A client can become disconnected from the server, and reconnect to the server * without loss of the session. Reconnection can be configured using * {@link SessionFactory#reconnectionStrategy the session factory}. The server * must be configured to allow reconnection. *

* If a session is connected to a server that belongs to a cluster with session * replication enabled, and then becomes disconnected, it will attempt to * reconnect to the original server. A properly configured load balancer can * detect that the original server is unavailable and re-route the reconnection * request to a second server in the cluster. The second server can recover * session data and continue the session. This process is known as "fail over". * Unlike reconnection, in-flight messages can be lost during failover, and the * application will be unsubscribed and re-subscribed to topics. * *

* The current state of the session can be retrieved with {@link #getState()}. A * listener can be registered with {@link #addListener(Listener)} which will be * notified when the session state changes. * *

Session properties

*

* For each session, the server stores a set of session properties that describe * various attributes of the session. *

* There are two types of session property. Fixed properties are assigned by * Diffusion. User-defined properties are assigned by the application. *

* Many operations use session filter expressions * that use session properties to select sessions. *

* A privileged client can monitor other sessions, including changes to their * session properties, using a {@link ClientControl#addSessionEventListener * session event listener}. When registering to receive session properties, * special key values of {@link #ALL_FIXED_PROPERTIES} and * {@link #ALL_USER_PROPERTIES} can be used. * *

* Each property is identified by a key. Most properties have a single string * value. The exception is the $Roles fixed property which has a set of string * values. *

* Fixed properties are identified by keys with a '$' prefix. The available * fixed session properties are: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
KeyDescription
{@code $ClientIP}The Internet address of the client in string format.
{@code $ClientType}The client type of the session. One of {@code ANDROID}, {@code C}, * {@code DOTNET}, {@code IOS}, {@code JAVA}, {@code JAVASCRIPT_BROWSER}, * {@code MQTT}, {@code PYTHON}, {@code REST}, or {@code OTHER}.
{@code $Environment}The environment in which the client is running. For possible values see the user * manual *
{@code $Connector}The configuration name of the server connector that the client connected * to.
{@code $Country}The country code for the country where the client's Internet address was * allocated (for example, {@code NZ} for New Zealand). Country codes are as * defined by {@link Locale}. If the country code could not be determined, this * will be a zero length string.
{@code $GatewayType}Gateway client type. Only set for gateway client sessions. If present it * indicates the type of gateway client (e.g. Kafka).
{@code $GatewayId}The identity of a gateway client session. Only present if the * $GatewayType session property is present.
{@code $Language}The language code for the official language of the country where the * client's Internet address was allocated (for example, {@code en} for * English). Language codes are as defined by {@link Locale}. If the language * could not be determined or is not applicable, this will be a zero length * string.
{@code $Latitude}The client's latitude, if available. This will be the string * representation of a floating point number and will be {@code NaN} if not * available.
{@code $Longitude}The client's longitude, if available. This will be the string * representation of a floating point number and will be {@code NaN} if not * available.
{@code $MQQTClientId}The MQTT client identifier. Only set for MQTT sessions. If present, the * value of the {@code $ClientType} session property will be {@code MQTT}.
{@code $Principal}The security principal associated with the client session.
{@code $Roles}Authorisation roles assigned to the session. This is a set of roles * represented as quoted strings (for example, {@code "role1","role2"}). The * utility method {@link Diffusion#stringToRoles(String)} can be used to parse * the string value into a set of roles.
{@code $ServerName}The name of the server to which the session is connected.
{@code $SessionId}The session identifier. Equivalent to {@link SessionId#toString()}.
{@code $StartTime}The session's start time in milliseconds since the epoch.
{@code $Transport}The session transport type. One of {@code WEBSOCKET}, * {@code HTTP_LONG_POLL}, {@code TCP}, or {@code OTHER}.
*

* All user-defined property keys are non-empty strings. The characters ' ', * '\t', '\r', '\n', '"', ''', '(', ')' are not allowed. *

* Session properties are initially associated with a session as follows:
*

    *
  1. When a client starts a new session, it can optionally propose * user-defined session properties (see * {@link SessionFactory#property(String, String)} and * {@link SessionFactory#properties(Map)}). Session properties proposed in this * way must be accepted by the authenticator. This safeguard prevents abuse by a * rogue, unprivileged client. *
  2. Diffusion allocates all fixed property values. *
  3. The new session is authenticated by registered authenticators. An * authenticator that accepts a session can veto or change the user-defined * session properties and add new user-defined session properties. The * authenticator can also change certain fixed properties. *
*

* Once a session is established, its user-defined session properties can be * modified by clients with {@code VIEW_SESSION} and {@code MODIFY_SESSION} * permissions using {@link ClientControl#setSessionProperties(SessionId, Map)}. * A privileged client can also modify its own session properties. *

* If a session re-authenticates (see * {@link Security#changePrincipal(String, Credentials) changePrincipal}), the * authenticator that allows the re-authentication can modify the user-defined * session properties and a subset of the fixed properties as mentioned above. * *

Session filters

* Session filters are a mechanism of addressing a set of sessions by the values * of their session properties. *

* Session filters are specified using a Domain Specific Language (DSL). For a full and * detailed description of the session filters DSL see the * * user manual. * *

Session locks

*

* The actions of multiple sessions can be coordinated using session locks. See * {@link SessionLock}. * * @author DiffusionData Limited * @since 5.0 */ public interface Session extends AutoCloseable { /** * Value returned by {@link #getPrincipal} if no principal name is * associated with the session. */ String ANONYMOUS = ""; /** * This constant can be used instead of a property key in requests for * session property values to indicate that all fixed session * properties are required. * * @since 5.6 */ String ALL_FIXED_PROPERTIES = "*F"; /** * This constant can be used instead of a property key in requests for * session property values to indicate that all user defined session * properties are required. * * @since 5.6 */ String ALL_USER_PROPERTIES = "*U"; /** * Session property key for session identifier. * * @since 6.2 */ String SESSION_ID = "$SessionId"; /** * Session property key for principal. * * @since 6.2 */ String PRINCIPAL = "$Principal"; /** * Session property key for connector name. * * @since 6.2 */ String CONNECTOR = "$Connector"; /** * Session property key for transport. * * @since 6.2 */ String TRANSPORT = "$Transport"; /** * Session property key for client type. * * @since 6.2 */ String CLIENT_TYPE = "$ClientType"; /** * Session property key for country code. * * @since 6.2 */ String COUNTRY = "$Country"; /** * Session property key for language code. * * @since 6.2 */ String LANGUAGE = "$Language"; /** * Session property key for server name. * * @since 6.2 */ String SERVER_NAME = "$ServerName"; /** * Session property key for client IP address. * * @since 6.2 */ String CLIENT_IP = "$ClientIP"; /** * Session property key for client latitude. * * @since 6.2 */ String LATITUDE = "$Latitude"; /** * Session property key for client longitude. * * @since 6.2 */ String LONGITUDE = "$Longitude"; /** * Session property key for client start time. * * @since 6.2 */ String START_TIME = "$StartTime"; /** * Session property key for session roles. * * @since 6.2 */ String ROLES = "$Roles"; /** * Session property key for MQTT client identifier. * * @since 6.6 */ String MQTT_CLIENT_ID = "$MQTTClientId"; /** * Session property key for gateway client type. * * @since 6.6 */ String GATEWAY_TYPE = "$GatewayType"; /** * Session property key for gateway client identifier. * * @since 6.6 */ String GATEWAY_ID = "$GatewayId"; /** * Session property key for client environment. * * @since 6.11 */ String ENVIRONMENT = "$Environment"; /** * Returns the unique identifier for the session as assigned by the (first) * server it connects to. * * @return the session identifier */ SessionId getSessionId(); /** * Returns the name of the security principal requested when opening the * session. * * @return the principal name. If the session was opened with no associated * principal, (it is an "anonymous session"), the empty string ( * {@link #ANONYMOUS}) will be returned * */ String getPrincipal(); /** * Returns the session attributes. * * @return session attributes */ SessionAttributes getAttributes(); /** * Returns the current state of the session. * * @return the current session state */ State getState(); /** * Close the session. *

* Has no effect if the session is already closed */ @Override void close(); /** * Obtain a feature. *

* This can be used to get any feature. It will automatically instantiate * the feature the first time it is called. * * @param featureInterface the feature interface * * @param feature type * * @return the feature * * @throws IllegalArgumentException if {@code featureInterface} is not an * interface * * @throws UnsupportedOperationException if no implementation of the feature * is supported or found */ T feature(Class featureInterface) throws IllegalArgumentException, UnsupportedOperationException; /** * Add a session listener. * * @param listener the listener * @since 5.1 */ void addListener(Listener listener); /** * Remove a session listener. All session listeners {@link Object#equals * equal} to {@code listener} will be removed. * * @param listener the listener * @since 5.1 */ void removeListener(Listener listener); // @formatter:off // @startuml // [*] --> AWAITING : Session.lock() // [*] -> OWNED : Session.lock() [lock already owned] /\n CompletableFuture completes with SessionLock \ // \nequal to the one provided when lock was acquired // AWAITING --> OWNED : lock acquired / CompletableFuture completes with new SessionLock for the acquisition // state OWNED : SessionLock.isOwned() == true // OWNED --> RELEASED : SessionLock.unlock() // OWNED --> RELEASED : session closed // OWNED --> RELEASED : connection lost [scope is UNLOCK_ON_CONNECTION_LOSS] // state RELEASED : SessionLock.isOwned() == false // AWAITING -> [*] : session closed / CompletableFuture completes with SessionClosedException // AWAITING -> [*] : no ACQUIRE_LOCK permission / CompletableFuture completes with SecurityException // AWAITING -> [*] : CompletableFuture canceled // @enduml // @formatter:on /** * Attempt to acquire a {@link SessionLock session lock}. *

* This method returns a CompletableFuture that will complete normally if * the server assigns the requested lock to the session. Otherwise, the * CompletableFuture will complete exceptionally with an exception * indicating why the lock could not be acquired. * *

* Acquiring the lock can take an arbitrarily long time if other sessions * are competing for the lock. The server will retain the session's request * for the lock until it is assigned to the session, the session is closed, * or the session cancels the CompletableFuture. * *

* A session can call this method multiple times. If the lock is acquired, * all calls will complete successfully with equal SessionLocks. * *

* Canceling the returned CompletableFuture has no effect on other pending * calls to {@code lock(...}} made by the session. * *

* If the CompletableFuture completes normally, the session owns the lock * and is responsible for unlocking it. When canceling a CompletableFuture, * take care that it has not already completed by checking the return value. * The following code releases the lock if the request could not be * canceled. * *

     * CompletableFuture result = session.lock("my-lock");
     *
     * // ..
     *
     * if (!result.cancel(true)) {
     *     // The session acquired the lock. Release it.
     *     SessionLock lock = result.get();
     *     lock.unlock();
     * }
     * 
* *

* A session that acquires a lock will remain its owner until it is * {@link SessionLock#unlock() unlocked} or the session closes. The * {@link #lock(String, SessionLockScope)} variant of this method takes a * scope parameter that provides the further option of releasing the lock * when the session loses its connection to the server. * *

Access control

*

* To allow fine-grained access control, lock names are interpreted as path * names, controlled with the {@link PathPermission#ACQUIRE_LOCK * ACQUIRE_LOCK} permission. This allows permission to be granted to a * session to acquire the lock {@code update-topic/a} while preventing the * session from acquiring the lock {@code update-topic/b}, for example. * * @param lockName the name of the session lock * @return a CompletableFuture that completes when a response is received * from the server. * *

* If this session has successfully acquired the session lock, or * this session already owns the session lock, the CompletableFuture * will complete normally with a SessionLock result. * *

* If the CompletableFuture completes exceptionally, this session * does not own the session lock. Common reasons for failure, * indicated by the exception reported as the * {@link CompletionException#getCause() cause}, include: * *

    *
  • {@link PermissionsException} – if the calling * session does not have the {@link PathPermission#ACQUIRE_LOCK * ACQUIRE_LOCK} permission for {@code lockName}; * *
  • {@link SessionClosedException} – if the session is * closed. *
* * @since 6.1 * @see SessionLock */ CompletableFuture lock(String lockName); /** * Variant of {@link #lock(String)} that provides control over when a lock * will be released. * *

* If called with {@link SessionLockScope#UNLOCK_ON_SESSION_LOSS * UNLOCK_ON_SESSION_LOSS}, this method behaves exactly like * {@link #lock(String)}. * *

* If called with {@link SessionLockScope#UNLOCK_ON_CONNECTION_LOSS * UNLOCK_ON_CONNECTION_LOSS}, any lock that is returned will be unlocked * if the session loses its connection to the server. This is useful to * allow another session to take ownership of the lock while this session is * reconnecting. * * @param lockName the name of the session lock * @param scope preferred scope. The scope of a lock controls when it will * be released automatically. If a session makes multiple requests * for a lock using different scopes, and the server assigns the lock * to the session fulfilling the requests, the lock will be given the * weakest scope (UNLOCK_ON_CONNECTION_LOSS). * @return a CompletableFuture that completes when a response is received * from the server. See {@link #lock(String)}. */ CompletableFuture lock(String lockName, SessionLockScope scope); /** * Values for the {@code scope} parameter of * {@link Session#lock(String, SessionLockScope)}. * * @since 6.1 */ enum SessionLockScope { /** * The lock will be released when the acquiring session loses its * current connection to the server. */ UNLOCK_ON_CONNECTION_LOSS, /** * The lock will be released when the acquiring session is closed. */ UNLOCK_ON_SESSION_LOSS, } /** * A session lock is a server-managed resource that can be used to * coordinate exclusive access to shared resources across sessions. For * example, to ensure a single session has the right to update a topic; to * ensure at most one session responds to an event; or to select a single * session to perform a housekeeping task. Session locks support general * collaborative locking schemes. The application architect is responsible * for designing a suitable locking scheme and for ensuring each application * component follows the scheme appropriately. * *

* Session locks are identified by a lock name. Lock names are arbitrary and * chosen at will to suit the application. Each lock is owned by at most one * session. Locks are established on demand; there is no separate operation * to create or destroy a lock. * *

* A session lock is acquired using the {@link Session#lock(String)} method. * If no other session owns the lock, the server will assign the lock to the * calling session immediately. Otherwise, the server will record that the * session is waiting to acquire the lock. A session can call {@code lock} * more than once for a given session lock – if the lock is acquired, * all calls will complete successfully with equal SessionLocks. * *

* If a session closes, the session locks it owns are automatically * released. A session can also {@link SessionLock#unlock() release a lock}. * When a session lock is released and other sessions are waiting to acquire * the lock, the server will arbitrarily select one of the waiting sessions * and notify it that it has acquired the lock. All of the newly selected * session's pending {@code lock} calls will complete normally. Other * sessions will continue to wait. * *

* The {@link #lock(String, SessionLockScope)} variant of this method takes * a scope parameter that provides the further option of automatically * releasing the lock when the session loses its connection to the server. * *

* The acquisition life cycle of a session lock from the perspective of a * session is shown in the following diagram. * *

* Session Lock life cycle. * *

Differences to java.util.concurrent.locks.Lock

* *

* Unlike the {@link java.util.concurrent.locks.Lock} API, there is no * association between a lock and a thread. If a session calls this method * for a lock it already owns, the call will complete normally and * immediately with a {@code SessionLock} that is equal to the one returned * when the lock was originally acquired. A single call to * {@link SessionLock#unlock()} will release this session's claim to a lock. * *

* A further difference to {@code java.util.concurrent.locks.Lock} is that * lock ownership can be lost due to an independent event such as loss of * connection, and not only due to the use of the locking API by the owner. * Consequently, the session should poll using {@link SessionLock#isOwned()} * to check that it still owns the lock before accessing the protected * resource. * *

Race conditions

*

* This session lock API has inherent race conditions. Even if an * application is coded correctly to protect a shared resource using session * locks, there may be a period where two or more sessions concurrently * access the resource. The races arise for several reasons including *

    *
  • due to the check-then-act approach of polling * {@code isOwned()}, the lock can be lost after the check has succeeded but * before the resource is accessed; *
  • the server can detect a session is disconnected and assign the lock * to another session before the original session has detected the * disconnection. *
*

* Despite this imprecision, session locks provide a useful way to * coordinate session actions. * * @since 6.1 */ interface SessionLock { /** * @return the name of the session lock */ String getName(); /** * A value that identifies the acquisition of the lock with the given * {@link #getName() name}. SessionLocks that are acquired later are * guaranteed to have bigger sequence values, allowing the sequence * number to be used as a fencing token. * * @return a value that identifies the acquisition of this lock */ long getSequence(); /** * Test whether the session lock is still owned. * * @return true if the session lock is still owned by the session */ boolean isOwned(); /** * The scope of the lock. * *

* The scope determines when the lock will be released automatically. * *

* If a session makes multiple * {@link Session#lock(String, SessionLockScope) requests for a lock} * using different scopes, and the server assigns the lock to the * session fulfilling the requests, the lock will be given the weakest * scope (UNLOCK_ON_CONNECTION_LOSS). Consequently, an individual * request can complete with a lock that has a different scope to that * requested. * * @return the lock scope * @see Session#lock(String, SessionLockScope) */ SessionLockScope getScope(); /** * Release a session lock, if owned. * * @return a CompletableFuture that completes when a response is * received from the server. * *

* On completion, this session will no longer own the named * session lock. If CompletableFuture completes normally, a true * value indicates this session previously owned the lock and a * false value indicates it did not. * *

* If the CompletableFuture completes exceptionally, this * session does not own the session lock. Common reasons for * failure, indicated by the exception reported as the * {@link CompletionException#getCause() cause}, include: * *

    *
  • {@link SessionClosedException} – if the session is * closed. *
* * @since 6.1 * @see #lock(String) */ CompletableFuture unlock(); } /** * The optional listener interface for a session which may be used to * receive state notifications. By default a session will not have a * listener. */ interface Listener { /** * Called whenever the state of a session changes. * * @param session the session * * @param oldState the old state * * @param newState the new state */ void onSessionStateChanged( Session session, State oldState, State newState); /** * Default {@link Listener} implementation which simply logs events at * debug level. */ class Default implements Session.Listener { private static final Logger LOG = LoggerFactory.getLogger(Session.Listener.Default.class); @Override public void onSessionStateChanged( Session session, State oldState, State newState) { LOG.debug( "{} - Session {} state changed from {} to {}", this, session, oldState, newState); } } } /** * The error notification interface for a session. *

* Session errors indicate that an unexpected condition has occurred. They * have a similar purpose to Java exceptions, except they are delivered * asynchronously. Session errors are used sparingly. Error conditions that * occur in the context of an API operation and that an application can * reasonably handle are reported to operation-specific callbacks, not as * session errors. *

* An application can typically do nothing about a session error other than * to report it for diagnosis. The server log should be examined for further * information. Errors may be a consequence of a failed client operation, so * it is usually appropriate for the application to close the session on * receiving an error. */ interface ErrorHandler { /** * Called when an error has occurred. * * @param session the session * * @param error the error detail */ void onError(Session session, SessionError error); /** * Default {@link ErrorHandler} implementation which simply logs errors * at error level. */ class Default implements Session.ErrorHandler { private static final Logger LOG = LoggerFactory.getLogger(Session.ErrorHandler.Default.class); @Override public void onError(Session session, SessionError error) { LOG.error( "{} - An error occurred for session {}: {}", this, session, error); } } } /** * Encapsulates the detail of a reported error. */ interface SessionError { /** * Returns a description of the error. * * @return a description of the error */ String getMessage(); /** * Returns a string representation of the error. * * @return the same as {@link #getMessage()} */ @Override String toString(); } /** * Session state. */ enum State { /** * The session is establishing its initial connection. */ CONNECTING(false, false, false), /** * An active connection with the server has been established. */ CONNECTED_ACTIVE(true, false, false), /** * Connection with a server has been lost and the session is attempting * reconnection. */ RECOVERING_RECONNECT(false, true, false), /** * The session has been closed by the client. */ CLOSED_BY_CLIENT(false, false, true), /** * The session has been closed (or rejected) by the server. */ CLOSED_BY_SERVER(false, false, true), /** * The session has lost its connection to a server and could not be * recovered. */ CLOSED_FAILED(false, false, true); private final boolean thisIsConnected; private final boolean thisIsRecovering; private final boolean thisIsClosed; /** * Constructor. */ State(boolean connected, boolean recovering, boolean closed) { thisIsConnected = connected; thisIsRecovering = recovering; thisIsClosed = closed; } /** * Returns true if a connected state. * * @return true if connected state */ public boolean isConnected() { return thisIsConnected; } /** * Returns true if a recovering state. * * @return true if recovering */ public boolean isRecovering() { return thisIsRecovering; } /** * Returns true if a disconnected state. * * @return true if disconnected */ public boolean isClosed() { return thisIsClosed; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy