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

net.sf.eBus.client.EMultiPublishFeed 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.Objects;
import java.util.logging.Level;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.util.regex.Pattern;

/**
 * This feed allows an {@link EPublisher} to open one feed for a
 * given notification message class and multiple message subjects.
 * It acts as a proxy between the publisher and the individual,
 * subordinate feeds. The publisher interacts solely with the
 * multi-key publisher. The publisher client opens, advertises,
 * un-advertises, and closes the multi-key feed. In turn, the
 * multi-key feed opens, advertises, un-advertises, and closes
 * the subordinate {@link EPublishFeed}s in unison
 * But the subordinate feeds issue
 * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)}
 * callbacks to the {@code EPublisher} registered with the
 * multi-key feed. The multi-key feed does not callback to the
 * publisher client. If the client opens a large number of
 * subordinate feeds, then the client must be prepared for a
 * large number of callbacks.
 * 

* The subordinate feeds are selected by either passing a * message class and subject list to * {@link #open(EPublisher, Class, List, EFeed.FeedScope) open} * or a notification message class and regular express query to * {@link #open(EPublisher, Class, Pattern, EFeed.FeedScope)}. * The first limits the subordinate feeds to exactly those whose * message key is listed. The second chooses message keys * with the given message class and whose subjects match the * regular expression. In either case, the publisher may * {@link #addFeed(String) add} or * {@link #closeFeed(String) remove} feeds dynamically while the * multi-key feed is open. When adding a new publish feed, the * new feed is configured in the same was as existing feeds and * put into the same state (open, advertised, etc.). *

* * @author Charles W. Rapp */ public final class EMultiPublishFeed extends EMultiFeed implements IEPublishFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Statics. // /** * Lambda expression used to create a new subordinate publish * feed. */ private static final SubordinateFeedFactory sSubFactory = (cl, key, sc, cond, loc) -> EPublishFeed.open(cl, key, sc, loc, true); /** * Lambda expression used to create a new multi-key publish * feed. */ private static final MultiFeedFactory sMultiFactory = (cl, mc, sc, cond, feeds) -> new EMultiPublishFeed(cl, mc, sc, feeds); //----------------------------------------------------------- // Locals. // /** * Tracks the publisher's ability to generate notification * messages for this feed. {@link #mFeedState} tracks * whether there are any subscribers to this feed. */ private EFeedState mPublishState; /** * Contains the functional interface callback for publish * status updates. If not explicitly set by client, then * defaults to * {@link EPublisher#publishStatus(EFeedState, EPublishFeed)}. * This callback is applied to each subordinate * {@code EPublishFeed}. */ private FeedStatusCallback mStatusCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new multiple key publish feed for the given * client, scope, and feeds. Note: {@code feeds} may be * dynamic meaning that new {@code EPublishFeed} instances * are added to the {@code feeds} list while this multi-key * feed is active. * @param client connect this client to the subordinate * {@code EPublishFeed}s. * @param mc all feeds apply to this notification message * class. * @param scope publish feed scope. * @param feeds initial subordinate publish feed list. */ private EMultiPublishFeed(final EClient client, final Class mc, final FeedScope scope, final Map feeds) { super (client, mc, scope, null, feeds); mPublishState = EFeedState.UNKNOWN; mStatusCallback = null; } // end of EMultiPublishFeed(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // IEPublishFeed Interface Implementations. // /** * Returns {@code true} if this multikey publish feed is both * open and advertised; otherwise, returns {@code false}. *

* Note: if {@code true} is returned, that does not * mean that the publisher is clear to publish notification * messages. The publisher should only post messages when * {@link #isFeedUp(String)} returns {@code true}. *

* @return {@code true} if this publish feed is open and * advertised. * * @see #isActive() * @see #isFeedUp(String) * @see #feedState(String) */ @Override public boolean isAdvertised() { return (mIsActive.get() && mInPlace); } // end of isAdvertise() /** * Returns the publish state. The returned state is not to be * confused with {@link #feedState(String)} which returns * {@link EFeedState#UP} if there is any subscriber to this * feed. The publish state specifies whether the publisher is * capable of publishing messages for this feed. *

* An up publish state does not mean that the publisher is * clear to post notifications to the feed. See * {@link #isFeedUp(String)} to determine if the publisher * may post notifications to a specific subject. *

* @return current publish state. * * @see #isFeedUp(String) */ @Override public EFeedState publishState() { return (mPublishState); } // end of publishState() /** * Returns {@code true} if the publisher is clear to publish * a notification for the given feed and {@code false} if not * clear. When {@code true} is returned, that means that this * feed is 1) open, 2) advertised, 3) the publish state is up, * and 4) there are subscribers listening to this * notification feed. *

* Returns {@code false} if {@code key} does not reference * a known message feed. *

* @param subject check the feed status for the feed * referenced by this subject. * @return {@code true} if the specified publisher feed is up * and the publisher is free to publish notification * messages for the specified subject. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-key feed is closed. * * @see #isActive() * @see #isAdvertised() */ @Override public boolean isFeedUp(final String subject) { Objects.requireNonNull(subject, "subject is null"); if (subject.isEmpty()) { throw ( new IllegalArgumentException( "subject is an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } final EPublishFeed feed = mFeeds.get(subject); return (feed != null && feed.isFeedUp()); } // end of isFeedUp(String) /** * Puts the publish status callback in place. If {@code cb} * is not {@code null}, publish status updates will be passed * to {@code cb} rather than * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)}. * The reverse is true if {@code cb} is {@code null}. That * is, a {@code null cb} means publish status updates are * posted to the * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)} * override. * @param cb the publish status update callback. May be * {@code null}. * @throws IllegalStateException * if this feed is either closed or advertised. */ @Override public void statusCallback(final FeedStatusCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } else if (mInPlace) { throw ( new IllegalStateException( "advertisement in place")); } mStatusCallback = cb; return; } // end of statusCallback(FeedStatusCallback<>) /** * Advertises each subordinate {@link EPublishFeed}. If this * feed is currently advertised, then does nothing. The * publisher client will receive a * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)} * callback for each subordinate publish feed. *

* The publisher may publish messages to this feed only * after: *

*
    *
  • * eBus calls * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)} * with an {@link EFeedState#UP up} feed state. *
  • *
  • * the publisher client calls * {@link #updateFeedState(EFeedState)} with an * {@code EFeedState.UP up} publish state. Note this * feed state is applied to all subordinate feeds. It is * not necessary for the publisher to call set the feed * state for individual feeds. *
  • *
*

* These two steps may occur in any order. Both states must * be up before {@link #publish(ENotificationMessage)} may * be called. *

* @throws IllegalStateException * if this feed is closed or the client did not override * {@link EPublisher} methods nor put the required callback * in place. * * @see #unadvertise() * @see #updateFeedState(EFeedState) * @see #close() */ @Override public void advertise() { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (!mInPlace) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s multi-key publisher %d: advertising (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Advertise each subordinate feed. mFeeds.values() .stream() .map( feed -> { feed.statusCallback(mStatusCallback); return feed; }) .forEachOrdered(EPublishFeed::advertise); // This feed is now advertised. mInPlace = true; } return; } // end of advertise() /** * Retracts this multi-key publisher feed by un-advertising * each subordinate publish feed. Does nothing if this * feed is not currently advertised. * @throws IllegalStateException * if this multi-key publisher feed is closed. * * @see #advertise() * @see #close() */ @Override public void unadvertise() { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s multi-key publisher %d: unadvertising (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Unadvertise each subordinate feed. mFeeds.values() .stream() .forEachOrdered(EPublishFeed::unadvertise); // This feed is no longer advertised. mPublishState = EFeedState.UNKNOWN; mInPlace = false; } return ; } // end of unadvertise() /** * Updates the publish feed state to the given value. If * {@code update} equals the currently stored publish feed * state, nothing is done. Otherwise, the updated value is * stored and the subordinate publish feed states are updated * as well. *

* The publish feed state may be updated only when this feed * is open and advertised. The method may not be called when * the feed is closed or un-advertised. *

* @param update the new publish feed state. * @throws NullPointerException * if {@code update} is {@code null}. * @throws IllegalStateException * if this feed was closed or is not advertised. */ @Override public void updateFeedState(final EFeedState update) { Objects.requireNonNull(update, "update is null"); if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } else if (!mInPlace) { throw ( new IllegalStateException( "feed not advertised")); } // Does this update actually change anything? else if (update != mPublishState) { // Yes. Apply the update. mPublishState = update; if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s multi-key publisher %d: setting feed state to %s (%s).", mEClient.location(), mEClient.clientId(), update, mScope)); } // Update each subordinate publish feed. mFeeds.values() .forEach(feed -> feed.updateFeedState(update)); } return; } // end of updateFeedState(EFeedState) /** * Posts a notification message to all subscribers via the * subordinate publish feed which matches the message's key. * @param msg post this message to the matching subordinate * feed. * @throws NullPointerException * if {@code msg} is {@code null}. * @throws IllegalArgumentException * if {@code msg} message key does not reference a known * subordinate publish feed. * @throws IllegalStateException * if this feed is inactive, not advertised, the publisher * has not declared the feed to be up, or there are no * subscribers listening to the subordinate feed. */ @Override public void publish(final ENotificationMessage msg) { // Is the message null? Objects.requireNonNull(msg, "msg is null"); // Is this feed still active? if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } // Is the advertisement in place? if (!mInPlace) { // No. Gotta do that first. throw ( new IllegalStateException( "feed not advertised")); } // Is the publisher state up? if (mPublishState != EFeedState.UP) { // No. Gotta do that second. throw ( new IllegalStateException( "publish state is down")); } // Does the message reference a known subordinate feed? final String subject = (msg.key()).subject(); if (!mFeeds.containsKey(subject)) { // No. throw ( new IllegalArgumentException( subject + " is an unknown feed")); } // So far, so good. Pass this message to the subordinate // feed and let it do its thing. (mFeeds.get(subject)).doPublish(msg); return; } // end of publish(ENotificationMessage) // // end of IEPublishFeed Interface Implementations. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Implementations. // /** * Returns a newly minted subordinate publish feed for the * given key. The returned feed's configuration is the same * as existing subordinate feeds. * @param key create feed for this key. * @return a subordinate publish feed. */ @Override protected EPublishFeed createFeed(final EMessageKey key) { final EPublisher publisher = (EPublisher) mEClient.target(); return (EPublishFeed.open(publisher, key, mScope, ClientLocation.LOCAL, true)); } // end of createFeed(EMessageKey) /** * Sets the feed status callback, advertises {@code feed}, * and updates the publish feed state. The publish feed state * is taken from the most recent * {@link #updateFeedState(EFeedState)} setting. If the * publish feed state has never been updated, then the state * is {@link EFeedState#UNKNOWN}. * @param feed advertise this feed. * * @see #updateFeedState(EFeedState) */ @Override protected void putFeedInPlace(final EPublishFeed feed) { feed.statusCallback(mStatusCallback); feed.advertise(); feed.updateFeedState(mPublishState); return; } // end of putFeedInPlace(EPublishFeed) // // end of Abstract Method Implementations. //----------------------------------------------------------- /** * Returns an open publish feed for multiple notification * message keys. Once opened, the caller can (optionally) set * the status callback and advertise the feed just like * {@link EPublishFeed}. *

* Note: {@code client} receives callbacks * for each subordinate {@code EPublishFeed} as if it opened * those feeds directly. If {@code subjects} is large, then * {@code client} must be prepared for a large number of * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)} * callbacks. *

*

* {@code subjects} may be a non-{@code null}, empty list * resulting in no initial subordinate publish feeds opened. * This allows the publisher to start with an empty * multi-key publisher feed, {@link #addFeed(String) adding} * subordinate feeds later. *

* @param client application object publishing the * notification message class and subjects. * @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 but this * list may be empty. * @param scope whether the feed supports local feeds, * remote feeds, or both. * @return a new multiple key publisher feed for the given * application object, notification message class, and * subjects. * @throws NullPointerException * if any of the arguments is {@code null}. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. * * @see #open(EPublisher, Class, Pattern, EFeed.FeedScope) * @see #statusCallback(FeedStatusCallback) * @see #advertise() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ public static EMultiPublishFeed open(final EPublisher client, final Class mc, final List subjects, final FeedScope scope) { return (openList(client, mc, subjects, scope, null, // no condition for publish feeds. sSubFactory, sMultiFactory)); } // end of open(EPublisher, List<>, FeedScope) /** * Returns an open publish feed for a notification message * class and multiple subjects. Once opened, the caller can * (optionally) set the status callback and advertise the * feed just like {@link EPublishFeed}. *

* The subordinate publish feeds are selected based on the * given notification message class and the regular * expression pattern. If message class and {@code query} do * not match any entries in the message key dictionary, then * the returned multi-key publish feed will have no initial * subordinate feeds. If that is the case, new publish feeds * may be dynamically * {@link EMultiFeed#addFeed(String) added} while * the multi-key feed is open. *

*

* Note: {@code client} receives callbacks * for each subordinate {@code EPublishFeed} as if it opened * those feeds directly. If {@code mc} and {@code query} * matches a large number of notification message keys, then * {@code client} must be prepared for a large number of * {@link EPublisher#publishStatus(EFeedState, IEPublishFeed)} * callbacks. *

* @param client the application object publishing the * notification message class and 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. * @return a new multiple key publisher feed for the given * application object and notification message keys matching * the query. * @throws NullPointerException * if any of the arguments are {@code null}. * @throws IllegalArgumentException * if any of the arguments is invalid. * * @see #open(EPublisher, Class, List, EFeed.FeedScope) * @see #statusCallback(FeedStatusCallback) * @see #advertise() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ public static EMultiPublishFeed open(final EPublisher client, final Class mc, final Pattern query, final FeedScope scope) { return (openQuery(client, mc, query, scope, null, sSubFactory, sMultiFactory)); } // end of open(EPublisher, Pattern, boolean, FeedScope) } // end of class EMultiPublishFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy