com.pushtechnology.diffusion.client.features.Topics Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2023 DiffusionData Ltd., All Rights Reserved.
*
* Use is subject to license terms.
*
* NOTICE: All information contained herein is, and remains the
* property of Push Technology. The intellectual and technical
* concepts contained herein are proprietary to Push Technology 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;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.slf4j.Logger;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.TimeSeries.Event;
import com.pushtechnology.diffusion.client.features.TimeSeries.RangeQuery;
import com.pushtechnology.diffusion.client.features.control.topics.SessionTrees;
import com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl;
import com.pushtechnology.diffusion.client.features.control.topics.views.TopicViews;
import com.pushtechnology.diffusion.client.session.PermissionsException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.session.SessionFactory;
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.topics.details.TopicType;
import com.pushtechnology.diffusion.client.types.PathPermission;
import com.pushtechnology.diffusion.datatype.Bytes;
import com.pushtechnology.diffusion.datatype.DataType;
import com.pushtechnology.diffusion.datatype.binary.Binary;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.recordv2.RecordV2;
/**
* This feature allows a client session to subscribe to topics to receive
* streamed topic updates, fetch the state of topics and/or update topics with
* new values.
*
* Specifically, the feature provides the ability to:
*
* - Subscribe to topics and specify streams to receive updates;
*
- Fetch the current state of topics (even if not subscribed);
*
- By extending the {@link TopicUpdate topic update feature}, update topics
* with new values;
*
- By extending the {@link TopicViews topic views feature}, manage topic
* views.
*
* Subscription and unsubscription
*
* A session can issue requests to subscribe to topics at any time, even if the
* topics do not exist at the server. Each subscription request provides a
* {@link TopicSelector topic selector} that is evaluated by the server to
* select matching topics. The session will be subscribed to any topics that
* match the selector unless they are already subscribed, or the session has
* insufficient permission. The subscription request is also retained at the
* server and the session will be automatically subscribed to newly created
* topics that match the selector (unless a subsequent unsubscription cancels
* the request).
*
* Sessions receive notifications from topics that they are subscribed to via
* subscription streams (see below). When a session is subscribed to a
* topic, all matching streams will first receive a subscription notification
* that provides details about the topic. If the server has a value for the
* topic, the value will be delivered to the streams before any other
* notifications.
*
* A session can unsubscribe from topics at any time. This is also specified
* using a topic selector. On unsubscription, matching streams are notified via
* the {@code onUnsubscription} notification. This notification will give the
* reason for unsubscription (for example, by request of the session, request of
* the server, or topic removal).
*
* Subscriptions and unsubscriptions can occur for reasons other than requests
* from the session. A session can be subscribed to or unsubscribed from a topic
* by another session using the {@link SubscriptionControl} feature. The removal
* of a topic also automatically causes unsubscription for subscribed sessions.
*
* Subscription requests are subject to authorization checks. The session must
* have {@link PathPermission#SELECT_TOPIC SELECT_TOPIC} permission for the
* topic selector used to subscribe. Matching topics will be further filtered to
* those for which the session has {@link PathPermission#READ_TOPIC READ_TOPIC}
* permission.
*
*
Subscription streams
*
* A session can listen to subscription events and updates for a selection of
* topics by adding one or more streams. A stream is registered using a topic
* selector which specifies the topics that the stream applies to. When an
* update is received for a topic then it will be routed to every stream that
* matches both the topic selector and the stream's value type. If more than one
* stream matches, all will receive the update; the order in which they are
* notified is not defined.
*
* A stream can be added several times for different selectors. If the same
* stream (determined by {@link Object#equals(Object) equals}) is registered for
* several selectors that match an event, the stream will only be notified of
* the event once. The mapping of topic selectors to streams is maintained
* locally in the client process.
*
* It is also possible to add one or more fallback streams which will
* receive updates that do not match any stream registered with a selector. This
* is useful for default processing or simply to catch unprocessed updates. A
* fallback stream can be added using {@link #addFallbackStream
* addFallbackStream}. Zero, one, or more fallback streams may be assigned. If
* no fallback stream is specified, any updates that are not routed to any other
* stream will simply be discarded.
*
* If the session is already subscribed to a topic when a matching stream is
* added, the stream will immediately receive a subscription notification. For
* most topic types, the latest value is locally cached and will be provided to
* the stream following the subscription notification.
*
* A stream will receive an
* {@link com.pushtechnology.diffusion.client.callbacks.Stream#onClose()
* onClose} callback when unregistered and an
* {@link com.pushtechnology.diffusion.client.callbacks.Stream#onError(ErrorReason)
* onError(SESSION_CLOSED)} callback if the session is closed.
*
*
Value streams
*
* A {@link ValueStream ValueStream} receives values for matching topics as and
* when updates are received from the server. Delta updates received from the
* server are automatically applied to locally cached values so that the stream
* always receives full values for each update.
*
*
* Value streams are typed to a specified value class and only updates for
* compatible topics will be routed to the stream. The following table shows how
* the value class maps to compatible topic types that will be routed to the
* stream:
*
*
*
* Value Class
* Compatible Topic Types
*
*
* {@link JSON}
* {@link TopicType#JSON JSON} {@link TopicType#STRING STRING}
* {@link TopicType#INT64 INT64} {@link TopicType#DOUBLE DOUBLE}
*
*
* {@link String}
* {@link TopicType#STRING STRING}
*
*
* {@link Long}
* {@link TopicType#INT64 INT64}
*
*
* {@link Double}
* {@link TopicType#DOUBLE DOUBLE}
*
*
* {@link Binary}
* {@link TopicType#BINARY BINARY}
*
*
* {@link Bytes}
* {@link TopicType#JSON JSON} {@link TopicType#STRING STRING}
* {@link TopicType#INT64 INT64} {@link TopicType#DOUBLE DOUBLE}
* {@link TopicType#BINARY BINARY} {@link TopicType#RECORD_V2 RECORD_V2}
*
*
* {@link RecordV2}
* {@link TopicType#RECORD_V2 RECORD_V2}
*
*
*
* Value stream implementations can be added using
* {@link #addStream(String, Class, ValueStream) addStream}.
*
*
* A value stream can be added to received updates from {@link TimeSeries time
* series topics} using {@link #addTimeSeriesStream(String, Class, ValueStream)
* addTimeSeriesStream}. The following table shows how the value class specified
* when adding the stream maps to the event value class of time series topics
* that will be routed to the stream:
*
*
*
* Event Value Class
* Time Series Event Value Class
*
*
* {@link JSON}
* {@link TopicType#JSON JSON} {@link TopicType#STRING STRING}
* {@link TopicType#INT64 INT64} {@link TopicType#DOUBLE DOUBLE}
*
*
* {@link String}
* {@link TopicType#STRING STRING}
*
*
* {@link Long}
* {@link TopicType#INT64 INT64}
*
*
* {@link Double}
* {@link TopicType#DOUBLE DOUBLE}
*
*
* {@link Binary}
* {@link TopicType#BINARY BINARY}
*
*
* {@link Bytes}
* {@link TopicType#JSON JSON} {@link TopicType#STRING STRING}
* {@link TopicType#INT64 INT64} {@link TopicType#DOUBLE DOUBLE}
* {@link TopicType#BINARY BINARY} {@link TopicType#RECORD_V2 RECORD_V2}
*
*
* {@link RecordV2}
* {@link TopicType#RECORD_V2 RECORD_V2}
*
*
*
* Fetch
*
* A session can issue a request to fetch details of a topic or topics (subject
* to authorization) at any time. The topics required are specified using a
* topic selector.
*
* The results of a fetch will return the topic path and type of each selected
* topic. The results may also optionally return the topic values and/or
* properties.
*
* A new request can be created using {@link #fetchRequest()} and modified to
* specify additional requirements of the fetch operation. The request is issued
* to the server using the {@link FetchRequest#fetch(TopicSelector) fetch}
* method on the request. This will return the results via a
* {@link CompletableFuture}.
*
*
*
Access control
*
* A session must have {@link PathPermission#SELECT_TOPIC SELECT_TOPIC}
* permission for the path prefix of the topic selector used to
* {@link #subscribe(TopicSelector) subscribe} or {@link #fetchRequest() fetch}.
* The topics that result from a subscription or fetch request are further
* filtered using the {@link PathPermission#READ_TOPIC READ_TOPIC} permission.
*
*
* No access control restrictions are applied to
* {@link #unsubscribe(TopicSelector) unsubscription}.
*
*
Accessing the feature
*
* This feature can be obtained from a {@link Session session} as follows:
*
*
* Topics topics = session.feature(Topics.class);
*
*
* @author DiffusionData Limited
* @since 5.0
*/
public interface Topics extends TopicUpdate, TopicViews, SessionTrees {
/**
* Add a value stream to receive topic events for topics that match a given
* {@link TopicSelector} and have a value class that matches a specified
* type. If the stream matches a time series topic with a compatible time
* series event type it will receive onValue events without metadata.
*
* See {@link Topics} class documentation for full details of the use of
* value streams.
*
* @param the value class
*
* @param topics selector of one or more topics
*
* @param valueClass the class of values that the stream accepts
*
* @param stream the stream to add
*
* @since 5.7
*/
void addStream(
TopicSelector topics,
Class extends V> valueClass,
ValueStream stream);
/**
* Add a value stream to receive topic events for topics that match a given
* {@link TopicSelector} expression and have a value class that matches a
* specified type. If the stream matches a time series topic with a
* compatible time series event type it will receive onValue events without
* metadata.
*
* See {@link Topics} class documentation for full details of the use of
* value streams.
*
* @param the value class
*
* @param topics as a {@link TopicSelector} expression
*
* @param valueClass the class of values that the stream accepts
*
* @param stream the stream to add
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @since 5.7
*/
void addStream(
String topics,
Class extends V> valueClass,
ValueStream stream)
throws IllegalArgumentException;
/**
* Add a fallback stream.
*
* See {@link Topics} class documentation for full details regarding the use
* of fallback streams.
*
* @param valueClass the class of values that the stream accepts
*
* @param stream the stream to add
*
* @since 5.7
*/
void addFallbackStream(
Class extends V> valueClass,
ValueStream stream);
/**
* Add a value stream to receive topic events for time series topics that
* match a given {@link TopicSelector} and have a compatible time series
* event value class.
*
* See the {@link Topics} class documentation for details of the use of
* value streams, and the {@link TimeSeries} class documentation for details
* of time series topics.
*
*
* This method must be used instead of
* {@link #addStream(TopicSelector, Class, ValueStream) addStream} to add a
* {@code ValueStream>} because there is no way to
* express a class literal of type {@code Class>}. The
* stream can be removed with {@link #removeStream}.
*
* @param the time series value class
*
* @param topics selector of one or more topics
*
* @param eventValueClass The type of event values accepted by this stream.
* The registration will match time series topics with a compatible
* event value class. See the {@link Topics} class documentation for
* details.
*
* @param stream the stream to add
*
* @since 6.0
*/
void addTimeSeriesStream(
TopicSelector topics,
Class extends V> eventValueClass,
ValueStream> stream);
/**
* Add a value stream to receive topic events for time series topics that
* match a given {@link TopicSelector} expression and have a compatible time
* series value class.
*
* See the {@link Topics} class documentation for details of the use of
* value streams, and the {@link TimeSeries} class documentation for details
* of time series topics.
*
*
* This method must be used instead of
* {@link #addStream(String, Class, ValueStream) addStream} to add a
* {@code ValueStream>} because Java has no way to
* express a class literal of type {@code Class>}. The
* stream can be removed with {@link #removeStream}.
*
* @param the time series value class
*
* @param topics as a {@link TopicSelector} expression
*
* @param eventValueClass The type of event values accepted by this stream.
* The registration will match time series topics with a compatible
* event value class. See the {@link Topics} class documentation for
* details.
*
* @param stream the stream to add
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @since 6.0
*/
void addTimeSeriesStream(
String topics,
Class extends V> eventValueClass,
ValueStream> stream)
throws IllegalArgumentException;
/**
* Remove a stream.
*
* More formally, this method removes all streams that compare equal to
* {@code stream}, regardless of the topic selector for which they are
* registered. It will also remove any fallback stream equal to
* {@code stream}. If there are no such streams, no changes are made.
*
* @param stream the value stream to remove
*
* @since 5.7
*/
void removeStream(
com.pushtechnology.diffusion.client.callbacks.Stream stream);
/**
* Request subscription to topics.
*
* The session will become subscribed to each existing topic matching the
* selector unless the session is already subscribed to the topic, or the
* session does not have {@link PathPermission#READ_TOPIC READ_TOPIC}
* permission for the topic path. For each topic to which the session
* becomes subscribed, a subscription notification and initial value (if
* any) will be delivered to registered value streams before the returned
* CompletableFuture completes.
*
*
* The subscription request is also retained at the server and the session
* will be automatically subscribed to newly created topics that match the
* selector (unless a subsequent unsubscription cancels the request).
*
* @param topics specifies the topics to request subscription to
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the task 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.
*
* @since 6.0
*/
CompletableFuture> subscribe(TopicSelector topics);
/**
* Request subscription to topics.
*
* This is equivalent to calling {@link #subscribe(TopicSelector)} with a
* selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics specifies the topics to request subscription to
* @throws IllegalArgumentException if topics is an invalid topic selector
* expression
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the task 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.
*
* @since 6.0
*/
CompletableFuture> subscribe(String topics);
/**
* Unsubscribe from topics.
*
* This can be used at any time whilst connected to reduce the set of topics
* to which the session is subscribed or negate earlier subscription
* requests.
*
* @param topics the topics to unsubscribe from
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the task 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 SessionClosedException} – if the session is
* closed.
*
* @since 6.0
*/
CompletableFuture> unsubscribe(TopicSelector topics);
/**
* Unsubscribe from topics.
*
* This is equivalent to calling {@link #unsubscribe(TopicSelector)} with a
* selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics the topics to unsubscribe from
* @throws IllegalArgumentException if topics is an invalid topic selector
* expression
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the task 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 SessionClosedException} – if the session is
* closed.
*
* @since 6.0
*/
CompletableFuture> unsubscribe(String topics);
/**
* Request subscription to topics.
*
* @param topics specifies the topics to request subscription to
*
* @param callback the callback object to receive status notifications for
* this operation
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void subscribe(
TopicSelector topics,
CompletionCallback callback);
/**
* Request subscription to topics.
*
* This is equivalent to calling
* {@link #subscribe(TopicSelector, CompletionCallback)} with a selector
* parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics a {@link TopicSelector} expression specifying the topics to
* request subscription to
*
* @param callback the callback object to receive status notifications for
* this operation
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void subscribe(String topics, CompletionCallback callback)
throws IllegalArgumentException;
/**
* Request subscription to topics.
*
* @param topics specifies the topics to request subscription to
*
* @param context an object passed to the callback with the reply to allow
* requests and replies to be correlated. The caller can use any
* convenient object reference, including {@code null}
*
* @param callback the callback object to receive status notifications for
* this operation
*
* @param context object type
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void subscribe(
TopicSelector topics,
C context,
CompletionContextCallback callback);
/**
* Request subscription to topics.
*
* This is equivalent to calling
* {@link #subscribe(TopicSelector, Object, CompletionContextCallback)} with
* a selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics a {@link TopicSelector} expression specifying the topics to
* request subscription to
*
* @param context an object passed to the callback with the reply to allow
* requests and replies to be correlated. The caller can use any
* convenient object reference, including {@code null}
*
* @param callback the callback object to receive status notifications for
* this operation
*
* @param context object type
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void subscribe(
String topics,
C context,
CompletionContextCallback callback)
throws IllegalArgumentException;
/**
* Unsubscribe from topics.
*
* This can be used at any time whilst connected to reduce the set of topics
* to which the session is subscribed or negate earlier subscription
* requests.
*
* @param topics the topics to unsubscribe from
*
* @param callback the callback handler for status notifications of this
* operation
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void unsubscribe(TopicSelector topics, CompletionCallback callback);
/**
* Unsubscribe from topics.
*
* This is equivalent to calling
* {@link #unsubscribe(TopicSelector, CompletionCallback)} with a selector
* parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics the topics to unsubscribe from
*
* @param callback the callback handler for status notifications of this
* operation
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void unsubscribe(String topics, CompletionCallback callback)
throws IllegalArgumentException;
/**
* Unsubscribe from topics.
*
* This can be used at any time whilst connected to reduce the set of topics
* to which the session is subscribed or negate earlier subscription
* requests.
*
* @param topics the topics to unsubscribe from
*
* @param context an object passed to the callback with the reply to allow
* requests and replies to be correlated. The caller can use any
* convenient object reference, including {@code null}
*
* @param callback the callback handler for status notifications of this
* operation
*
* @param context object type
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void unsubscribe(
TopicSelector topics,
C context,
CompletionContextCallback callback);
/**
* Unsubscribe from topics.
*
* This is equivalent to calling
* {@link #unsubscribe(TopicSelector, Object, CompletionContextCallback)}
* with a selector parsed using {@link TopicSelectors#parse(String)}.
*
* @param topics the topics to unsubscribe from
*
* @param context an object passed to the callback with the reply to allow
* requests and replies to be correlated. The caller can use any
* convenient object reference, including {@code null}
*
* @param callback the callback handler for status notifications of this
* operation
*
* @param context object type
*
* @throws IllegalArgumentException if {@code topics} is not a valid
* selector expression
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
void unsubscribe(
String topics,
C context,
CompletionContextCallback callback)
throws IllegalArgumentException;
/**
* Callback interface for success or failure notifications from subscription
* and unsubscription operations.
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
interface CompletionCallback extends Callback {
/**
* Called to indicate that the requested operation has been processed by
* the server.
*/
void onComplete();
/**
* Default implementation of {@link CompletionCallback}.
*
* This logs onComplete calls at 'debug' level. This method can be
* overridden to perform some more specific action.
*/
class Default
extends Callback.Default
implements CompletionCallback {
private static final Logger LOG =
getLogger(CompletionCallback.Default.class);
@Override
public void onComplete() {
LOG.debug("{} - Completed subscribe/unsubscribe", this);
}
}
}
/**
* Contextual callback interface for success or failure notifications from
* subscription operations.
*
* Use this alternative to {@link CompletionCallback} to associate some
* arbitrary context object with each call.
*
* @param context object type
*
* @deprecated since 6.7
*
* Methods that use callbacks are deprecated and will be removed
* in a future release. Use CompletableFuture variant instead.
*/
@Deprecated
interface CompletionContextCallback extends ContextCallback {
/**
* Called to indicate that a requested operation has been processed by
* the server.
*
* @param context the context object supplied when making the call. May
* be {@code null}
*/
void onComplete(C context);
/**
* Default implementation of {@link CompletionContextCallback}.
*
* This logs onComplete calls at 'debug' level. This method can be
* overridden to perform some more specific action.
*
* @param context object type
*/
class Default
extends ContextCallback.Default
implements CompletionContextCallback {
private static final Logger LOG =
getLogger(CompletionContextCallback.Default.class);
@Override
public void onComplete(C context) {
LOG.debug(
"{} - Completed subscribe/unsubscribe, context={}",
this,
context);
}
}
}
/**
* Base subscriber stream interface.
*
* See {@link ValueStream} and {@link Topics} class documentation for
* further details of the use of subscriber streams.
*
* @since 5.7
*/
interface SubscriberStream
extends com.pushtechnology.diffusion.client.callbacks.Stream {
/**
* Subscription notification.
*
* This method is called when a session is subscribed to a topic that
* matches the stream registration. This method is also called when a
* stream is added, for all of the session's subscriptions to topics
* that match the stream registration.
*
* For a given topic, {@code onSubscription} will be the initial
* notification, and the first notification following an
* {@link #onUnsubscription unsubscription} notification if the session
* re-subscribes to the topic.
*
* This method is also called for fallback streams that match the topic
* type when the session removes the last stream that selected a
* subscribed topic. The fallback stream will now receive updates for
* the topic, starting with an immediate notification of the currently
* cached value (if any).
*
* @param topicPath the topic path
*
* @param specification the topic specification
*/
void onSubscription(String topicPath, TopicSpecification specification);
/**
* Unsubscription notification.
*
*
* This method is called if the session is unsubscribed from a topic
* that matches the stream registration. The stream will receive no more
* updates for the topic unless the session re-subscribes to the topic.
*
*
* This method is also called for fallback streams that match the topic
* type if the session
* {@link Topics#addStream(String, Class, ValueStream) adds} the first
* stream that selects a subscribed topic. For these notifications,
* {@code reason} will be {@link UnsubscribeReason#STREAM_CHANGE}. The
* fallback stream will no longer receive updates for the topic.
*
* @param topicPath the topic path
*
* @param specification the topic specification
*
* @param reason the reason for unsubscription
*/
void onUnsubscription(
String topicPath,
TopicSpecification specification,
UnsubscribeReason reason);
/**
* Default {@link Topics.SubscriberStream} implementation.
*
* This logs calls to onSubscription and onUnsubscription at 'debug'
* level. These implementations can be useful during development but are
* usually overridden to provide meaningful processing.
*/
abstract class Default
extends
com.pushtechnology.diffusion.client.callbacks.Stream.Default
implements SubscriberStream {
private static final Logger LOG =
getLogger(SubscriberStream.Default.class);
@Override
public void onSubscription(
String topicPath,
TopicSpecification specification) {
LOG.debug(
"{} - Topic {} subscribed : {}",
this,
topicPath,
specification);
}
@Override
public void onUnsubscription(
String topicPath,
TopicSpecification specification,
UnsubscribeReason reason) {
LOG.debug(
"{} - Topic {} unsubscribed : {}",
this,
topicPath,
reason);
}
}
}
/**
* Stream interface that can be registered to receive subscription and value
* events whenever an update is received from the server.
*
* A stream implementation can be registered using
* {@link Topics#addStream(String, Class, ValueStream) addStream}. The
* stream will receive events for the topics that are selected by the topic
* selector and have a compatible type.
*
* A stream implementation can also be registered as a fallback stream using
* {@link Topics#addFallbackStream(Class, ValueStream) addFallbackStream}.
* Fallback streams will receive events for all topics that are not selected
* by other streams the session has registered using {@link #addStream
* addStream}.
*
* If the stream is {@link Topics#removeStream removed},
* {@link com.pushtechnology.diffusion.client.callbacks.Stream#onClose()
* onClose} will be called.
*
* If the session is closed,
* {@link com.pushtechnology.diffusion.client.callbacks.Stream#onError(ErrorReason)
* onError} will be called with {@link ErrorReason#SESSION_CLOSED}.
*
* See {@link Topics} class documentation for further details of the use of
* value streams.
*
* @param the value class
* @since 5.7
*/
interface ValueStream extends SubscriberStream {
/**
* Notifies an update to a topic value from the server.
*
* This is also called to provide the current value for any matching
* topic that the session is already subscribed to when the stream is
* added.
*
* @param topicPath the topic path
*
* @param specification the topic specification
*
* @param oldValue the previous value. Will be null for the initial call
* to onValue for a topic. It can also be null if the topic's
* data type supports null values.
*
* @param newValue the new value derived from the last update received
* from the server. It can be null if the topic's data type
* supports null values.
*/
void onValue(
String topicPath,
TopicSpecification specification,
V oldValue,
V newValue);
/**
* Default {@link Topics.ValueStream} implementation.
*
* This logs all calls at 'debug' level. These implementations can be
* useful during development but are usually overridden to provide
* meaningful processing.
*
* @param the value class
*/
class Default extends SubscriberStream.Default
implements ValueStream {
private static final Logger LOG =
getLogger(ValueStream.Default.class);
@Override
public void onValue(
String topicPath,
TopicSpecification specification,
V oldValue,
V newValue) {
LOG.debug(
"{} - Value received for topic {} : {} {}",
this,
topicPath,
oldValue,
newValue);
}
}
}
/**
* The reason that an unsubscription occurred.
*/
enum UnsubscribeReason {
/**
* Unsubscribed by the subscribing session.
*/
REQUESTED,
/**
* The unsubscription was requested either by another session or by the
* server.
*/
CONTROL,
/**
* The unsubscription occurred because the topic was removed.
*/
REMOVAL,
/**
* The unsubscription occurred because the session is no longer
* authorized to access the topic.
*
* @since 5.9
*/
AUTHORIZATION,
/**
* The server has re-subscribed this session to the topic. Existing
* streams are unsubscribed because the topic type and other attributes
* may have changed.
*
*
* This can happen if a set of servers is configured to use session
* replication, and a session connected to one server reconnects (
* "fails over") to a different server.
*
* @since 5.9
*/
SUBSCRIPTION_REFRESH,
/**
* A fallback stream has been unsubscribed due to the addition of a
* stream that selects the topic.
*
* @since 5.9
*/
STREAM_CHANGE,
/**
* The server has a significant backlog of messages for the session, and
* the topic specification has the {@link TopicSpecification#CONFLATION
* conflation topic property} set to "unsubscribe". The session can
* resubscribe to the topic. The unsubscription is not persisted to the
* cluster. If the session fails over to a different server it will be
* resubscribed to the topic.
*/
BACK_PRESSURE,
/**
* The unsubscription occurred because branch mapping rules changed.
*
* @since 6.7
* @see SessionTrees
*/
BRANCH_MAPPINGS,
/**
* A reason that is unsupported by the session.
*
* @since 6.1
*/
UNKNOWN_UNSUBSCRIBE_REASON,
}
/**
* Creates an unconfigured fetch request.
*
* The returned request can be invoked with
* {@link FetchRequest#fetch(TopicSelector) fetch}. The server will evaluate
* the query and return a fetch result that provides the paths and types of
* the matching topics which the session has
* {@link PathPermission#READ_TOPIC READ_TOPIC} permission.
*
* You will usually want to restrict the query to a subset of the topic
* tree, and to retrieve the topic values and/or properties. This is
* achieved by applying one or more of the fluent builder methods provided
* by {@code FetchRequest} to produce more refined requests.
*
* For example:
*
*
* FetchResult result =
* topics.fetchRequest().withValues(String.class).fetch("*A/B//").get();
*
*
* @see FetchRequest
*
* @return a new unconfigured fetch request
*
* @since 6.2
*/
FetchRequest fetchRequest();
/**
* A parameterised query that can be used to search the topic tree.
*
* A new request can be created using the {@link Topics#fetchRequest()
* fetchRequest} method and modified to specify a range of topics and/or
* various levels of detail. The request can then be issued to the server
* using the {@link FetchRequest#fetch(TopicSelector) fetch} method
* supplying a topic selector which specifies the selection of topics. The
* results are returned via a {@link CompletableFuture}.
*
* As a minimum, the path and type of each selected topic will be returned.
* It is also possible to request that the topic {@link #withValues values}
* and/or {@link #withProperties properties} are returned.
*
* If values are selected then the topic types selected are naturally
* constrained by the provided {@code valueClass} argument. So if
* String.class
is specified, only {@link TopicType#STRING
* STRING} topics will be selected. However, if JSON.class
is
* specified, all types compatible with {@link JSON} will be selected
* including {@link TopicType#STRING STRING}, {@link TopicType#INT64 INT64}
* and {@link TopicType#DOUBLE DOUBLE}. See
* {@link DataType#canReadAs(Class)} for the class hierarchy of types.
* Selecting a value class of Object.class
will return values
* for all topic types.
*
* If values are requested, the request and results are typed to the value
* type, otherwise the type is {@code Void}. To select topic types when
* values are not required, or to further constrain the selection when
* values are required, it is also possible to specify exactly which
* {@link #topicTypes topic types} to select.
*
* The values of {@link TopicType#TIME_SERIES time series} topics are only
* returned if the specified value class is {@code Object}. In this case the
* value returned represents the latest event appended to the topic and is
* of type {@link Event Event<Bytes>} The bytes value from the event
* can then be read using the {@link DataType} corresponding to the time
* series event value type of the topic. A {@link RangeQuery time series
* range query} can be used to retrieve all the events of a time series
* topic.
*
* The topics selected by the topic selector can be further restricted by
* range. A range is defined by a start path and an end path, and contains
* all paths in-between in path order. Given a topic tree containing the
* topics:
*
*
* a, a/b, a/c, a/c/x, a/c/y, a/d, a/e, b, b/a/x, b/b/x, c
*
*
*
* The range from {@code a/c/y} to {@code b/a/x} includes the topics with
* paths:
*
*
* a/c/x, a/c/y, a/d, a/e, b, b/a/x
*
*
* The start point of a range can be specified using {@link #from from} or
* {@link #after after} and an end point using {@link #to to} or
* {@link #before before}. {@link #from from} and {@link #to to} include any
* topic with the specified path in the selection, whereas {@link #after
* after} and {@link #before before} are non-inclusive and useful for paging
* through a potentially large range of topics. If no start point is
* specified, the start point is assumed to be the first topic of the topic
* tree, ordered by path name. Similarly, if no end point is specified, the
* end point is the last topic of the topic tree.
*
* A limit on the number of results returned can be specified using
* {@link #first first}. This is advisable if the result set could
* potentially be large. The number of results returned is also limited by
* the session's maximum message size – see {@link #maximumResultSize}. The
* result indicates whether the results have been limited via the
* {@link Topics.FetchResult#hasMore() hasMore} method. If {@code hasMore}
* returns true, further results can be retrieved by modifying the original
* query to request results {@link #after} the last path received.
*
* By default, results are returned in path order, earliest path first,
* starting from the beginning of any range specified. It is also possible
* to request results from the end of the range indicated by specifying a
* limit to the number of results using {@link #last last}. This method
* complements {@link #first}, returning up to the specified number of
* results from the end of the range, but in reverse path order. This is
* useful for paging backwards through a range of topics.
*
* It can be useful to explore an unknown topic tree in a breadth-first
* manner rather than the path order. This can be achieved using
* {@link #limitDeepBranches}.
*
* {@link TopicType#ROUTING Routing} topics are not supported, and if
* encountered will be ignored (i.e. treated as if they did not exist).
*
* FetchRequest instances are immutable and can be safely shared and reused.
*
* @since 6.2
* @param The value type of the request.
*/
interface FetchRequest {
/**
* A constant set of all topic types that can be fetched.
*/
Set ALL_TYPES =
Collections.unmodifiableSet(
EnumSet.of(
TopicType.JSON,
TopicType.BINARY,
TopicType.RECORD_V2,
TopicType.DOUBLE,
TopicType.INT64,
TopicType.STRING,
TopicType.TIME_SERIES));
/**
* Specifies a logical start point within the topic tree.
*
* If specified, only results for topics with a path that is lexically
* equal to or 'after' the specified path will be returned.
*
* This is the inclusive equivalent of {@link #after after} and if used
* will override any previous {@link #after after} or {@link #from from}
* constraint.
*
* @param topicPath the topic path from which results are to be returned
*
* @return a new fetch request derived from this fetch request but
* selecting only topics from the specified path onwards
* (inclusive)
*/
FetchRequest from(String topicPath);
/**
* Specifies a logical start point within the topic tree.
*
* If specified, only results for topics with a path that is lexically
* 'after' the specified path will be returned.
*
* This is the non-inclusive equivalent of {@link #from from} and if
* used will override any previous {@link #from from} or {@link #after
* after} constraint.
*
* @param topicPath the topic path after which results are to be
* returned
*
* @return a new fetch request derived from this fetch request but
* selecting only topics after the specified path (not
* inclusive)
*/
FetchRequest after(String topicPath);
/**
* Specifies a logical end point within the topic tree.
*
* If specified, only results for topics with a path that is lexically
* equal to or 'before' the specified path will be returned.
*
* This is the inclusive equivalent of {@link #before before} and if
* used will override any previous {@link #before before} or {@link #to
* to} constraint.
*
* @param topicPath the topic path to which results are to be returned
*
* @return a new fetch request derived from this fetch request but
* selecting only topics including and before the specified path
* (inclusive)
*/
FetchRequest to(String topicPath);
/**
* Specifies a logical end point within the topic tree.
*
* If specified, only results for topics with a path that is lexically
* 'before' the specified path will be returned.
*
* This is the non-inclusive equivalent of {@link #to to} and if used
* will override any previous {@link #to to } or {@link #before before}
* constraint.
*
* @param topicPath the topic path before which results are to be
* returned
*
* @return a new fetch request derived from this fetch request but
* selecting only topics before the specified path (not
* inclusive)
*/
FetchRequest before(String topicPath);
/**
* Specifies that only topics of the specified topic types should be
* returned.
*
* If this is not specified, {@link #ALL_TYPES all types} will be
* returned (unless constrained by {@link #withValues withValues}).
*
* If the specified topic type matches the event type of a time series
* topic it will also be returned. The value will be delivered without
* the associated metadata. To specify all time series topics use
* {@link TopicType#TIME_SERIES}
*
* This may be used instead to further constrain the results when using
* {@link #withValues withValues}. For example, you can specify
* JSON.class
to {@link #withValues withValues} then
* specify {@link TopicType#JSON JSON} here to ensure that only JSON
* topics are returned and not those topics that are logically value
* subtypes of JSON (e.g. {@link TopicType#STRING STRING}).
*
* If {@link #withValues withValues} has been specified then the types
* specified here must be compatible with the value class specified or
* the event type for time series topics.
*
* {@link TopicType#ROUTING ROUTING} may not be specified.
*
* @param topicTypes topic types to be selected
*
* @return a new fetch request derived from this fetch request but
* specifying that only topics of the specified topic types
* should be returned
*
* @throws IllegalArgumentException if topicTypes contains
* {@link TopicType#ROUTING ROUTING}
*/
FetchRequest topicTypes(Set topicTypes);
/**
* Specifies that values should be returned for selected topics,
* constraining the selection to only those topics with a data type
* compatible with the specified value class.
*
* If Object.class
is specified, values will be returned
* for all topic types (unless constrained by {@link #topicTypes
* topicTypes}).
*
* The specified value constrains the topic types. So, any topic types
* specified in a previous call to {@link #topicTypes topicTypes} that
* cannot be read as the specified class will be removed from the list
* of topic types.
*
* @param valueClass the class of values. If {@code null} is specified
* this will cancel any previous call (topic types will remain
* unchanged).
*
* @return a new fetch request derived from this fetch request but
* specifying that only topics compatible with the specified
* class should be returned with values
*
* @throws IllegalArgumentException if the class is not compatible with
* any topic types.
*/
FetchRequest withValues(Class extends T> valueClass);
/**
* Specifies that all properties associated with each topic's
* {@link TopicSpecification specification} should be returned.
*
* @return a new fetch request derived from this fetch request but
* specifying that topic specification properties should be
* returned
*/
FetchRequest withProperties();
/**
* Include the details of reference topics that are not yet published.
*
*
* {@link TopicViews Topic views} that use the {@code delay by} clause
* create reference topics in an unpublished state. The topics are
* published once the delay time has expired. A topic in the
* unpublished state prevents a lower priority topic view from creating
* a reference topic with the same path.
*
*
* A reference topic in the unpublished state which matches the query
* will only be included in the fetch results if the session has
* {@link PathPermission#READ_TOPIC READ_TOPIC} permission for the
* reference's source topic as well as {@code READ_TOPIC} permission for
* the reference topic. Requiring {@code READ_TOPIC} permission for the
* source topic ensures less privileged sessions cannot derive
* information from the existence of the reference topic before the
* delay time has expired.
*
* @return a new fetch request derived from this fetch request,
* additionally specifying that unpublished reference topics
* should be included in the results
* @since 6.5
*/
FetchRequest withUnpublishedDelayedTopics();
/**
* Specifies a maximum number of topic results to be returned from the
* start of the required range.
*
* If this is not specified, the number of results returned will only be
* limited by other constraints of the request.
*
* This should be used to retrieve results in manageable batches and
* prevent very large result sets.
*
* If there are potentially more results that would satisfy the other
* constraints, then the fetch result will indicate so via the
* {@link FetchResult#hasMore hasMore} method.
*
* Zero can be supplied to return no results. Such a request can be used
* together with {@link FetchResult#hasMore} to query whether there are
* topics that match the selector provided to
* {@link FetchRequest#fetch}, without retrieving the details of any of
* the topics. To retrieve unlimited topics use Integer.MAX_VALUE which
* is the default value.
*
* Either this or {@link #last last} may be specified. This will
* therefore override any previous {@link #last last} or {@link #first
* first} constraint.
*
* @param number the maximum number of results to return from the start
* of the range
*
* @return a new fetch request derived from this fetch request but
* selecting only the number of topics specified from the start
* of the range
*/
FetchRequest first(int number);
/**
* Specifies a maximum number of topic results to be returned from the
* end of the required range.
*
* This is similar to {@link #first first} except that the specified
* number of results are returned from the end of the range. This is
* useful for paging backwards through a range of topics. Results are
* always returned in topic path order (not reverse order).
*
* Zero can be supplied to return no results. Such a request can be used
* together with {@link FetchResult#hasMore} to query whether there are
* topics that match the selector provided to
* {@link FetchRequest#fetch}, without retrieving the details of any of
* the topics. To retrieve unlimited topics use Integer.MAX_VALUE which
* is the default value.
*
* Either this or {@link #first first} may be specified. This will
* therefore override any previous {@link #first first} or {@link #last
* last} constraint.
*
* @param number the maximum number of results to return from the end of
* the range
*
* @return a new fetch request derived from this fetch request but
* selecting only the number of topics specified from the end of
* the range
*/
FetchRequest last(int number);
/**
* Specifies the maximum data size of the result set.
*
* This may be used to constrain the size of the result. If not
* specified then by default the maximum message size for the session
* (as specified by {@link SessionFactory#maximumMessageSize(int)} is
* used.
*
* @param maximumSize the maximum size of the result set in bytes. If a
* value greater than the session's maximum message size is
* specified, the maximum message size will be used.
*
* @return a new fetch request derived from this fetch request but
* constraining the size of the result to the specified maximum
*/
FetchRequest maximumResultSize(int maximumSize);
/**
* Specifies a limit on the number of results returned for each deep
* branch.
*
*
* A deep branch has a root path that has a number of parts equal to the
* {@code deepBranchDepth} parameter. The {@code deepBranchLimit}
* specifies the maximum number of results for each deep branch.
*
*
* This method is particularly useful for incrementally exploring a
* topic tree from the root, allowing a breadth-first search strategy.
*
*
* For example, given a topic tree containing the topics with the
* following paths:
*
*
* x/0
* x/x/1
* x/x/x/2
* y/y/y/y/3
* y/y/y/4
* z/5
* z/z/6
*
*
*
* Then
*
*
* FetchResult result =
* topics.fetchRequest().limitDeepBranches(1, 1).fetch("?.//").get();
*
*
* will return results with the paths {@code x/0}, {@code y/y/y/y/3},
* and {@code z/5}. The application can then determine the roots of the
* tree are {@code x}, {@code y}, and {@code z}.
*
*
* The {@code deepBranchLimit} parameter can usefully be set to
* {@code 0}. For example, given the same example topic tree,
*
*
* FetchResult result =
* topics.fetchRequest().limitDeepBranches(3, 0).fetch("?.//").get();
*
*
* will only return results having paths with fewer than three parts;
* namely {@code x/0}, and {@code z/5}.
*
*
* The fetch result does not indicate whether this option caused some
* results to be filtered from deep branches. It has no affect on the
* {@link FetchResult#hasMore() hasMore()} result. If the result set
* contains {@code deepBranchLimit} results for a particular deep
* branch, some topics from that branch may have been filtered.
*
* @param deepBranchDepth the number of parts in the root path of a
* branch for it to be considered deep
* @param deepBranchLimit the maximum number of results to return for
* each deep branch
* @return a new fetch request derived from this fetch request but
* restricting the number of results for deep branches
* @since 6.4
*/
FetchRequest limitDeepBranches(
int deepBranchDepth,
int deepBranchLimit);
/**
* Sends a fetch request to the server.
*
* Results are returned for all topics matching the selector that
* satisfy the request constraints within any range defined by
* {@link #from from}/{@link #after after} and/or {@link #to
* to}/{@link #before before}.
*
* @param topics specifies a topic selector which selects the topics to
* be fetched
*
* @return a CompletableFuture that completes when a response is
* received from the server with the results of the fetch
* operation.
*
* If the task completes successfully, the CompletableFuture
* result will be an object encapsulating all of the results.
*
* 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> fetch(TopicSelector topics);
/**
* Sends a fetch request to the server.
*
* This is the equivalent of {@link #fetch(TopicSelector)}, with the
* convenience of directly specifying the selector as a String.
*
* @param topics specifies a topic selector string which selects the
* topics to be fetched
*
* @return a CompletableFuture that completes when a response is
* received from the server with the results of the fetch
* operation.
*
* If the task completes successfully, the CompletableFuture
* result will be an object encapsulating all of the results.
*
* 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> fetch(String topics);
}
/**
* Encapsulates the results from a fetch operation issued to the server.
*
* A fetch operation is issued using a {@link FetchRequest fetch request}
* which will return a result of this type via a {@link CompletableFuture}.
*
* @param The result value type. This would be Void unless the request
* indicated that {@link FetchRequest#withValues values} are to be
* returned, in which case this would be the value class requested.
*
* @since 6.2
*/
interface FetchResult {
/**
* Returns the results from the fetch operation.
*
* Results are always returned in path order.
*
* @return a list of topic results, each representing a result single
* topic selected by the fetch operation.
*/
List> results();
/**
* Indicates whether the fetch could have returned more results if it
* had not been constrained by the {@link FetchRequest#first first},
* {@link FetchRequest#last last} or
* {@link FetchRequest#maximumResultSize maximumResultSize} limits.
*
* A fetch that requests no results can be used together with this
* method to query whether there are topics that match the selector
* provided to {@link FetchRequest#fetch}, without retrieving the
* details of any of the topics.
*
* For example:
* {@code
* topics.fetchRequest().first(0).fetch("?x//").thenAccept(fetchResult -> {
* if (fetchResult.hasMore()) {
* System.out.println("There are topics in branch 'x'.");
* }
* }
* }
*
* @return true if more results could have been returned, otherwise
* false
*/
boolean hasMore();
/**
* The number of elements in the fetch result.
*
* @return the size of the results list
* @since 6.3
*/
int size();
/**
* Returns true
if the result contains zero elements.
*
* @return true if result list is empty
* @since 6.3
*/
boolean isEmpty();
/**
* Encapsulates the result of a {@link FetchRequest#fetch(TopicSelector)
* fetch} invocation for a single selected topic.
*
* @param The result value type. This would be Void unless the
* request indicated that {@link FetchRequest#withValues values}
* are to be returned, in which case this would be the value
* class requested.
*/
interface TopicResult {
/**
* Returns the topic path.
*
* @return the topic path
*/
String path();
/**
* Returns the topic type.
*
* This is a convenience method equivalent to calling
* {@code specification().getType()}.
*
* @return the topic type
*/
TopicType type();
/**
* Returns the topic value.
*
* This will only return a value if the fetch request specified
* {@link FetchRequest#withValues withValues} and the topic actually
* had a value. For topics that have no value this will return null.
*
* @return the topic value or null if none available
*/
V value();
/**
* Returns the topic specification.
*
* If the request specified {@link FetchRequest#withProperties()
* withProperties}, the result reflect the topic's specification and
* can be used to create an identical topic. If the request did not
* specify {@link FetchRequest#withProperties() withProperties}, the
* specification's property map will be empty.
*
* @return the topic specification
*/
TopicSpecification specification();
}
}
}