
net.sf.eBus.client.EMultiSubscribeFeed Maven / Gradle / Ivy
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright 2017. Charles W. Rapp
// All Rights Reserved.
//
package net.sf.eBus.client;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import static net.sf.eBus.client.EMultiFeed.sLogger;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.util.regex.Pattern;
/**
* This feeds acts as a proxy for handling multiple
* {@link ESubscribeFeed}s on behalf of a {@link ESubscriber}
* client. A subscriber opens a multi-key subscribe feed for a
* specified notification message class and zero or more message
* subjects. There subjects may be specified as a list or a
* regular expression query If a query is used, then the message
* class and subject query are used to to search the message key
* dictionary for all matching subjects. The matching subjects
* are used to create the initial subordinate
* {@code ESubjectFeed}s.
*
* The multi-key subscribe feed coordinates the subordinate
* subscribe feeds to they are given the same configuration and
* are in the same state (open, subscribed, un-subscribed,
* closed).
*
*
* While the multi-key feed is open, new subordinate subscribe
* feeds may be {@link #addFeed(String) added to} or
* {@link #closeFeed(String) removed from} the multi-key feed.
* Newly added subordinate feeds are configured and put into the
* same state as the existing subordinate feeds.
*
*
* @author Charles W. Rapp
*/
public final class EMultiSubscribeFeed
extends EMultiFeed
implements IESubscribeFeed
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* Lambda expression used to create a new subordinate
* subscribe feed.
*/
private static final SubordinateFeedFactory
sSubFactory =
(cl, key, sc, cond, loc) ->
ESubscribeFeed.open(cl, key, sc, cond, loc, true);
/**
* Lambda expression used to create a new multi-key subscribe
* feed.
*/
private static final MultiFeedFactory
sMultiFactory =
(cl, mc, sc, cond, feeds) ->
new EMultiSubscribeFeed(cl, mc, sc, cond, feeds);
//-----------------------------------------------------------
// Locals.
//
/**
* Feed status callback. If not explicitly set by client,
* then defaults to
* {@link ESubscriber#feedStatus(EFeedState, ESubscribeFeed)}.
* Applied to all subordinate subscribe feeds.
*/
private FeedStatusCallback mStatusCallback;
/**
* Notification message callback. If not explicity set by
* client, then defaults to
* {@link ESubscriber#notify(ENotificationMessage, ESubscribeFeed)}.
* Applied to all subordinate subscribe feeds.
*/
private NotifyCallback mNotifyCallback;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new multi-key subscribe feed instance for the
* given client, scope, feeds, and condition. Note:
* {@code feeds} may be dynamic meaning that new
* {@code ESubscribeFeed} instances may be added to the
* {@code feeds} list while this multi-key feed is active.
* @param client connect this client to the subordinate
* {@code ESubscribeFeed}s.
* @param mc all feeds apply to this notification message
* class.
* @param scope subscribe feed scope.
* @param condition subscribe condition applied to all
* subordinate feeds.
* @param feeds initial subordinate subscribe feed list.
*/
private EMultiSubscribeFeed(final EClient client,
final Class extends ENotificationMessage> mc,
final FeedScope scope,
final ECondition condition,
final Map feeds)
{
super (client, mc, scope, condition, feeds);
mStatusCallback = null;
mNotifyCallback = null;
} // end of EMultiSubscribeFeed(...)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// IESubscribeFeed Interface Implementations.
//
/**
* Puts the feed status callback in place. If {@code cb}
* is not {@code null}, feed status updates will be passed
* to {@code cb} rather than
* {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed)}.
* The reverse is true if {@code cb} is {@code null}. That
* is, a {@code null cb} means feed status updates are
* posted to the
* {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed)}
* override.
*
* The status callback is applied to all subordinate
* {@code ESubscribeFeed}s. This means that each subordinate
* feed calls back the same method.
*
* @param cb the feed status update callback. May be
* {@code null}.
* @throws IllegalStateException
* if this feed is either closed or subscribed.
*/
@Override
public void statusCallback(final FeedStatusCallback cb)
{
if (!mIsActive.get())
{
throw (
new IllegalStateException("feed is inactive"));
}
if (mInPlace)
{
throw (
new IllegalStateException(
"subscription in place"));
}
mStatusCallback = cb;
return;
} // end of statusCallback(FeedStatusCallback<>)
/**
* Puts the notification message callback in place. If
* {@code cb} is not {@code null}, then notification messages
* will be passed to {@code cb} rather than
* {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed)}.
* A {@code null cb} means that notification messages will be
* passed to the
* {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed)}
* override.
*
* The notify callback is applied to all subordinate
* {@code ESubscribeFeed}s. This means that each subordinate
* feed passes inbound notification messages to the same
* method.
*
* @param cb pass notification messages back to application
* via this callback.
* @throws IllegalStateException
* if this feed is either closed or subscribed.
*/
@Override
public void notifyCallback(final NotifyCallback cb)
{
if (!mIsActive.get())
{
throw (
new IllegalStateException("feed is inactive"));
}
if (mInPlace)
{
throw (
new IllegalStateException(
"subscription in place"));
}
mNotifyCallback = cb;
return;
} // end of notifyCallback(NotifyCallback)
/**
* Subscribes each subordinate {@link ESubscribeFeed}. If
* this feed is currently subscribed, then does nothing. The
* subscriber client will receive a
* {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed)}
* callback from each subordinate subscribe feed.
* @throws IllegalStateException
* if this feed is closed or the client did not override
* {@link ESubscriber} methods nor put the required callback
* in place.
*
* @see #unsubscribe()
* @see #close()
*/
@Override
public void subscribe()
{
if (!mIsActive.get())
{
throw (
new IllegalStateException("feed is inactive"));
}
if (!mInPlace)
{
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format(
"%s multi-key subscriber %d: subscribing (%s).",
mEClient.location(),
mEClient.clientId(),
mScope));
}
// Subscribe each subordinate feed.
mFeeds.values()
.stream()
.map(
feed ->
{
feed.statusCallback(mStatusCallback);
feed.notifyCallback(mNotifyCallback);
return (feed);
})
.forEachOrdered(ESubscribeFeed::subscribe);
// This feed is now advertised.
mInPlace = true;
}
return;
} // end of subscribe()
/**
* Retracts this multi-key subscribe feed by un-subscribing
* each subordinate subscribe feed. Does nothing if this
* feed is not currently subscribed.
* @throws IllegalStateException
* if this multi-key subscribe feed is closed.
*
* @see #subscribe()
* @see #close()
*/
@Override
public void unsubscribe()
{
if (!mIsActive.get())
{
throw (
new IllegalStateException("feed is inactive"));
}
if (mInPlace)
{
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format(
"%s multi-key subscriber %d: unsubscribing (%s).",
mEClient.location(),
mEClient.clientId(),
mScope));
}
// Unadvertise each subordinate feed.
mFeeds.values()
.stream()
.forEachOrdered(ESubscribeFeed::unsubscribe);
// This feed is no longer subscribed.
mInPlace = false;
}
return;
} // end of unsubscribe()
//
// end of IESubscribeFeed Interface Implementations.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Implementations.
//
/**
* Returns a newly minted subordinate subscribe feed for the
* given key.
* @param key create feed for this key.
* @return a subordinate subscribe feed.
*/
@Override
protected ESubscribeFeed createFeed(final EMessageKey key)
{
final ESubscriber subscriber =
(ESubscriber) mEClient.target();
return (ESubscribeFeed.open(subscriber,
key,
mScope,
mCondition,
ClientLocation.LOCAL,
true));
} // end of createFeed(EMessageKey)
/**
* Sets the callbacks and subscribes the {@code feed}.
* @param feed subscribe this feed.
*/
@Override
protected void putFeedInPlace(final ESubscribeFeed feed)
{
// Must set the callbacks (if any) before subscribing.
feed.statusCallback(mStatusCallback);
feed.notifyCallback(mNotifyCallback);
feed.subscribe();
return;
} // end of putFeedInPlace(ESubscribeFeed)
//
// end of Abstract Method Implementations.
//-----------------------------------------------------------
/**
* Returns an open multi-key subscribe feed for the given
* notification message class and multiple subjects. Once
* opened, the caller can (optionally) set the status and
* notify callbacks and subscribe the feed just like
* {@link ESubscribeFeed}.
*
* Note: {@code client} receives callbacks
* for each subordinate {@code ESubscribeFeed} as if it
* opened all those feeds directly. If {@code keys} contains
* a large number of notification message keys, then
* {@code client} must be prepared for feed status and notify
* callbacks for each of the subordinate subscribe feeds.
*
*
* {@code subjects} may be a non-{@code null}, empty list
* resulting in no initial subordinate subscribe feeds opened.
* This allows the subscriber to start with an empty
* multi-key subscriber feed, {@link #addFeed(String) adding}
* subordinate feeds later.
*
* @param client the application object publishing the
* notification message class and subject.
* @param mc notification message class. All feeds apply to
* this message class.
* @param subjects list of notification message subjects.
* May not contain {@code null} or empty strings.
* @param scope whether the feed supports local feeds,
* remote feeds, or both.
* @param condition accept notification messages only if the
* messages passes this condition. May be {@code null}. If
* {@code null}, then the
* {@link ESubscribeFeed#NO_CONDITION default condition}
* which accepts all messages is used.
* @return a new multiple key subscribe feed for the given
* application object and notification message keys.
* @throws NullPointerException
* if any of the required arguments is {@code null}.
* @throws IllegalArgumentException
* if {@code subjects} contains an empty string.
*
* @see #open(ESubscriber, Class, Pattern, EFeed.FeedScope, ECondition)
* @see #statusCallback(FeedStatusCallback)
* @see #notifyCallback(NotifyCallback)
* @see #subscribe()
* @see #addFeed(String)
* @see #closeFeed(String)
* @see EMultiFeed#close()
*/
public static EMultiSubscribeFeed open(final ESubscriber client,
final Class extends ENotificationMessage> mc,
final List subjects,
final FeedScope scope,
final ECondition condition)
{
return (openList(client,
mc,
subjects,
scope,
condition,
sSubFactory,
sMultiFactory));
} // end of open(ESubscriber,Class,List<>,FeedScope,ECondition)
/**
* Returns an open multi-key subscribe feed for a given
* notification message class and multiple message subjects.
* Once opened, the caller can (optionally) set the status
* and notify callbacks and subscribe the feed just like
* {@link ESubscribeFeed}.
*
* The subordinate subscribe feeds are selected based on the
* given notification message class and the regular
* expression query. The multi-key subscribe feed is opened
* whether the message key dictionary entries match the
* message class and subject query or not. In either case,
* the application may {@link #addFeed(String) add} more
* subordinate feeds to the returned multi-key feed.
*
*
* Note: {@code client} receives callbacks
* from subordinate {@code ESubscribeFeed} feeds, not the
* multi-key feed. If {@code mc} and {@code query} match
* 1,000 notification message keys, then {@code client} will
* receive feed status and notify callbacks from those 1,000
* subordinate feeds.
*
* @param client application object subscribing to the
* notification message class and matching subjects.
* @param mc the message key query is for this notification
* message class only.
* @param query message key subject query.
* @param scope whether the feed supports local feeds, remote
* feeds, or both.
* @param condition accept notification messages only if the
* messages passes this condition. May be {@code null}. If
* {@code null}, then the
* {@link ESubscribeFeed#NO_CONDITION default condition}
* which accepts all messages is used.
* @return a new multiple key subscribe feed for the given
* application object and notification message keys matching
* the notification message class and query.
* @throws NullPointerException
* if any of the arguments are {@code null}.
* @throws IllegalArgumentException
* if any of the arguments is invalid.
*
* @see #open(ESubscriber, Class, List, EFeed.FeedScope, ECondition)
* @see #statusCallback(FeedStatusCallback)
* @see #notifyCallback(NotifyCallback)
* @see #addFeed(String)
* @see #closeFeed(String)
* @see #subscribe()
* @see #close()
*/
public static EMultiSubscribeFeed open(final ESubscriber client,
final Class extends ENotificationMessage> mc,
final Pattern query,
final FeedScope scope,
final ECondition condition)
{
return (openQuery(client,
mc,
query,
scope,
condition,
sSubFactory,
sMultiFactory));
} // end of open(...)
} // end of class EMultiSubscribeFeed