com.pushtechnology.diffusion.client.features.control.topics.TopicNotifications Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2017, 2023 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.features.control.topics;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pushtechnology.diffusion.client.callbacks.Registration;
import com.pushtechnology.diffusion.client.callbacks.Stream;
import com.pushtechnology.diffusion.client.session.Feature;
import com.pushtechnology.diffusion.client.session.PermissionsException;
import com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.topics.TopicSelector;
import com.pushtechnology.diffusion.client.topics.TopicSelectors;
import com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.client.types.PathPermission;
/**
* This feature allows a client session to receive notifications about changes
* to selected topics.
*
* Notifications
Sessions receive notifications via
* {@link TopicNotifications.TopicNotificationListener
* TopicNotificationListener}s. The listener will be provided with the
* {@link TopicSpecification specifications} for all topics bound to paths that
* match registered selectors, and any subsequent notifications for the selected
* topics on those paths, via
* {@link TopicNotifications.TopicNotificationListener#onTopicNotification
* onTopicNotificaion}. Notifications will only be emitted for paths where a
* topic is bound.
*
* For example, with a registered selector {@code "?a//"}, if a topic is added
* at path {@code a/b/c/d} with no topics bound to paths higher in the hierarchy
* {@link TopicNotifications.TopicNotificationListener#onTopicNotification
* onTopicNotification} will be called once with a topic path of
* {@code "a/b/c/d"}, a notification type of
* {@link TopicNotifications.TopicNotificationListener.NotificationType#ADDED
* ADDED}, and the topic's associated {@link TopicSpecification}.
*
* The nature of the notification is provided by the
* {@link TopicNotifications.TopicNotificationListener.NotificationType
* NotificationType} enum.
* {@link TopicNotifications.TopicNotificationListener.NotificationType#ADDED
* ADDED} and
* {@link TopicNotifications.TopicNotificationListener.NotificationType#REMOVED
* REMOVED} represent structural changes to the topic tree;
* {@link TopicNotifications.TopicNotificationListener.NotificationType#SELECTED
* SELECTED} indicates that a pre-existing topic has been selected by a new
* registered selector, and similarly
* {@link TopicNotifications.TopicNotificationListener.NotificationType#DESELECTED
* DESELECTED} indicates that a topic is no longer selected because of changes
* to the set of registered selectors for the listener.
*
Selection and deselection
Registered
* {@link TopicNotifications.TopicNotificationListener
* TopicNotificationListeners} will receive notifications for all topics
* matching registered selections. Selection of topics using
* {@link TopicSelector} expressions is provided via the
* {@link NotificationRegistration} associated for a specific listener.
*
* A session can request selections at any time, even if the topics do not exist
* at the server. Selections are stored on the server and any subsequently added
* topics that match registered selectors will generate notifications.
*
Immediate descendant notifications
Listeners will be informed about
* the presence or absence of unselected immediate descendants via
* {@link TopicNotifications.TopicNotificationListener#onDescendantNotification
* onDescendantNotification}. This allows listeners to determine whether to
* select deeper topic paths in order to walk the topic tree. An immediate
* descendant is defined as the first bound topic on any branch below a given
* topic path.
*
* For example, for topics at {@code "a/b", "a/c", "a/c/d", "a/e/f/g"}, the
* immediate descendants of {@code "a"} would be {@code "a/b", "a/c", "a/e/f/g"}
* .
*
* Immediate descendant notifications provide a
* {@link TopicNotifications.TopicNotificationListener.NotificationType
* NotificationType} to indicate the reason for the notification in the same
* manner as
* {@link TopicNotifications.TopicNotificationListener#onTopicNotification
* onTopicNotification}.
*
* For example, with a registered selector {@code ">a"}, if a topic is added at
* path {@code a/b} then
* {@link TopicNotifications.TopicNotificationListener#onDescendantNotification
* onDescendantNotification} will be called with a topic path of {@code "a/b"}
* and a notification type of
* {@link TopicNotifications.TopicNotificationListener.NotificationType#ADDED
* ADDED}. If a topic was subsequently added at path {@code a/b/c}, no further
* notifications will be received until {@link NotificationRegistration#select}
* was used to select the deeper topic path {@code ">a/b"}.
*
*
Access control
A listener will only be notified about topics for
* which the session has {@link PathPermission#SELECT_TOPIC SELECT_TOPIC} and
* {@link PathPermission#READ_TOPIC READ_TOPIC} permissions.
* {@link PathPermission#SELECT_TOPIC SELECT_TOPIC} determines which selectors
* a listener may register; {@link PathPermission#READ_TOPIC READ_TOPIC}
* determines which selected topics the client may receive notifications for.
*
* @author DiffusionData Limited
* @since 6.0
*/
public interface TopicNotifications extends Feature {
/**
* Register a listener to receive topic notifications.
*
* @param listener the listener to receive topic specification notifications
*
* @return A completable future providing the registration state
*
* If the registration completes successfully, the CompletableFuture
* result will be a {@link NotificationRegistration}, which may be
* used to later close the listener and remove it from the server.
*
* Otherwise, the CompletableFuture will complete exceptionally with
* a {@link CompletionException}. Common reasons for failure, listed
* by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture addListener(TopicNotificationListener listener);
/**
* The NotificationRegistration represents the registration state of the
* associated listener on the server.
*
* The NotificationRegistration also provides operations to control which
* topic paths the listener will receive notifications for.
*/
interface NotificationRegistration extends Registration {
/**
* Request to receive notifications for all topics matched by the
* provided topic selector.
*
* @param selector the selector to register
* @return a CompletableFuture that completes when a response is
* received from the server.
*
* If the selection completes successfully, the
* CompletableFuture result will be null. The result type is
* any rather than Void to provide forward compatibility with
* future iterations of this API that may provide a non-null
* result with a more specific result type.
*
*
* Otherwise, the CompletableFuture will complete exceptionally
* with a {@link CompletionException}. Common reasons for
* failure, listed by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link PermissionsException} – if the calling
* session does not have {@code SELECT_TOPIC} permission for the
* path prefix of the selector expression;
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture> select(TopicSelector selector);
/**
* Request to receive notifications for all topics matched by the
* provided selector.
*
* This is equivalent to calling {@link #select(TopicSelector)} with a
* selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param selector the selector to register
* @return a CompletableFuture that completes when a response is
* received from the server.
*
* If the selection completes successfully, the
* CompletableFuture result will be null. The result type is
* any rather than Void to provide forward compatibility with
* future iterations of this API that may provide a non-null
* result with a more specific result type.
*
*
* Otherwise, the CompletableFuture will complete exceptionally
* with a {@link CompletionException}. Common reasons for
* failure, listed by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link PermissionsException} – if the calling
* session does not have {@code SELECT_TOPIC} permission for the
* path prefix of the selector expression;
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture> select(String selector);
/**
* Request to stop receiving notifications for all topics matched by the
* given selector.
*
* @param selector the selector to register
* @return a CompletableFuture that completes when a response is
* received from the server.
*
* If the deselection completes successfully, the
* CompletableFuture result will be null. The result type is
* any rather than Void to provide forward compatibility with
* future iterations of this API that may provide a non-null
* result with a more specific result type.
*
*
* Otherwise, the CompletableFuture will complete exceptionally
* with a {@link CompletionException}. Common reasons for
* failure, listed by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link PermissionsException} – if the calling
* session does not have {@code SELECT_TOPIC} permission for the
* path prefix of the selector expression;
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture> deselect(TopicSelector selector);
/**
* Request to stop receiving notifications for all topics matched by the
* given selector.
*
* This is equivalent to calling {@link #deselect(TopicSelector)} with a
* selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param selector the selector to register
* @return a CompletableFuture that completes when a response is
* received from the server.
*
* If the deselection completes successfully, the
* CompletableFuture result will be null. The result type is
* any rather than Void to provide forward compatibility with
* future iterations of this API that may provide a non-null
* result with a more specific result type.
*
*
* Otherwise, the CompletableFuture will complete exceptionally
* with a {@link CompletionException}. Common reasons for
* failure, listed by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link PermissionsException} – if the calling
* session does not have {@code SELECT_TOPIC} permission for the
* path prefix of the selector expression;
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture> deselect(String selector);
}
/**
* Listener for topic notifications.
*/
interface TopicNotificationListener extends Stream {
/**
* Notification for an immediate descendant of a selected topic path.
* This notifies the presence or absence of a descendant topic that may
* subsequently be explicitly selected.
*
* @param topicPath the path of the immediate descendant that is not selected
* @param type the type of notification
*/
void onDescendantNotification(String topicPath, NotificationType type);
/**
* A notification for a selected topic.
*
* @param topicPath the path of the topic that this notification is for
* @param specification the specification of the topic that this
* notification is for
* @param type the type of notification
*/
void onTopicNotification(
String topicPath,
TopicSpecification specification,
NotificationType type);
/**
* The type of notification that has been received.
*/
enum NotificationType {
/**
* The topic has been added.
*/
ADDED,
/**
* The topic existed at the time of the selector registration.
*/
SELECTED,
/**
* The topic has been removed.
*/
REMOVED,
/**
* The topic is no longer selected due to the removal of a selector.
*/
DESELECTED
}
/**
* Default listener.
*
* This simply logs {@link TopicNotificationListener} method calls at
* {@code debug} level. This class may be extended to provide more
* specific processing.
*/
class Default extends Stream.Default
implements TopicNotificationListener {
private static final Logger LOG =
LoggerFactory.getLogger(
TopicNotificationListener.Default.class);
@Override
public void onTopicNotification(
String topicPath,
TopicSpecification specification,
NotificationType type) {
LOG.debug(
"Topic notification: path={}, specification={}, type={}",
topicPath, specification, type);
}
@Override
public void onDescendantNotification(
String topicPath,
NotificationType type) {
LOG.debug("Descendant notification: path={}, type={}",
topicPath, type);
}
}
}
}