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

net.sf.eBus.client.ESubscribeFeed 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 2015, 2016, 2018. Charles W. Rapp
// All Rights Reserved.
//

package net.sf.eBus.client;

import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;

/**
 * {@code ESubscribeFeed} is the application entry point for
 * receiving {@link ENotificationMessage notification messages}.
 * Follow these steps to use this feed:
 * 

* Step 1: Implement the {@link ESubscriber} * interface. *

*

* Step 2: * {@link #open(ESubscriber, EMessageKey, EFeed.FeedScope, ECondition) Open} * a subscribe feed for a given {@code ESubscriber} instance and * {@link EMessageKey type+topic message key}. The condition is * optional and may be {@code null}. If provided, then only * notification messages satisfying the condition are forwarded * to the subscriber. *

*

* Step 3 (optional): Do not override * {@link ESubscriber} interface methods. Instead, set callbacks * using {@link #statusCallback(FeedStatusCallback)} and/or * {@link #notifyCallback(NotifyCallback)} passing in Java lambda * expressions. *

*

* Step 4: {@link #subscribe() Subscribe} to the * open feed. *

*

* Step 5: Wait for an * {@link EFeedState#UP up} * {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed) feed status}. * This callback will occur before any notification messages are * delivered. If the feed state is {@link EFeedState#DOWN down}, * then no notifications will be delivered until the feed state * comes back up. *

*

* Step 6: Once the feed state is up, wait for * {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed) notification messages} * to arrive. *

*

* Step 7: When the subscriber is shutting down, * {@link #unsubscribe() retract} the subscription and * {@link #close() close} the feed. *

*

Example use of {@code ESubscribeFeed}

*
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.ESubscribeFeed;
import net.sf.eBus.client.ESubscriber;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;

Step 1: Implement the ESubscriber interface.
public class CatalogSubscriber implements ESubscriber
{
    // Subscribe to this notification message class/subject key and feed scope.
    private final EMessageKey mKey;
    private final FeedScope mScope;

    // Store the feed here so it can be used to unsubscribe.
    private ESubscribeFeed mFeed;

    public CatalogSubscriber(final String subject, final FeedScope scope) {
        mKey = new EMessageKey(CatalogUpdate.class, subject); mScope = scope;
        mFeed = null;
    }

    @Override public void startup() {
        try {
        Step 2: Open the ESubscribe feed.
            // This subscription has no associated ECondition; passing in null.
            mFeed = ESubscribeFeed.open(this, mKey, mScope, null);

            Step 3: ESubscriber interface method overridden.

            Step 4: Subscribe to the feed.
            mFeed.subscribe();
        } catch(IllegalArgumentException argex) {
            // Feed open failed. Place recovery code here.
        }
    }

    Step 5: Wait for EFeedState.UP feed status.
    @Override public void feedStatus(final EFeedState feedState, final IESubscribeFeed feed) {
        // What is the feed state?
        if (feedState == EFeedState.DOWN) {
            // Down. There are no publishers. Expect no notifications until a
            // publisher is found. Put error recovery code here.
        } else {
            // Up. There is at least one publisher. Expect to receive notifications.
        }
    }

    Step 6: Wait for notifications to arrive.
    @Override public void notify(final ENotificationMessage msg, final IESubscribeFeed feed) {
        Notification handling code here.
    }

    @Override public void shutdown() {
    Step 7: When subscriber is shutting down, retract subscription feed.
        // mFeed.unsubscribe() is not necessary since close() will unsubscribe.
        if (mFeed != null) {
            mFeed.close();
            mFeed = null;
        }
    }
}
* * * @see ESubscriber * @see EPublisher * @see EPublishFeed * * @author Charles W. Rapp */ public final class ESubscribeFeed extends ENotifyFeed implements IESubscribeFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed)} * method name. */ public static final String FEED_STATUS_METHOD = "feedStatus"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(ESubscribeFeed.class.getName()); //----------------------------------------------------------- // Locals. // /** * Use this condition to check if the notification message * should be forwarded to subscriber. */ private final ECondition mCondition; /** * Feed status callback. If not explicitly set by client, * then defaults to * {@link ESubscriber#feedStatus(EFeedState, ESubscribeFeed)}. */ private FeedStatusCallback mStatusCallback; /** * Notification message callback. If not explicity set by * client, then defaults to * {@link ESubscriber#notify(ENotificationMessage, ESubscribeFeed)}. */ private NotifyCallback mNotifyCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new subscribe feed instance for the given * client and subject. * @param client the client making this subscription. * @param scope the subscription scope. * @param condition the optional subscription condition. * May be {@code null}. * @param subject the subscription is for this notification * subject. */ private ESubscribeFeed(final EClient client, final FeedScope scope, final ECondition condition, final ENotifySubject subject) { super (client, scope, FeedType.SUBSCRIBE_FEED, subject); mCondition = condition; mStatusCallback = null; mNotifyCallback = null; } // end of ESubscribeFeed(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Overrides. // @Override /* package */ int updateActivation(final ClientLocation loc, final EFeedState fs) { boolean updateFlag = false; int retval = 0; // Does this feed support the contra-feed's location? if (mScope.supports(loc)) { // Yes. Update the activation count based on the // feed state. // Increment or decrement? if (fs == EFeedState.UP) { // Increment. ++mActivationCount; retval = 1; // Update? updateFlag = (mActivationCount == 1); } // Decrement. else if (mActivationCount > 0) { --mActivationCount; retval = -1; // Update? updateFlag = (mActivationCount == 0); } // Did this feed transition between inactivation and // activation? if (updateFlag) { // Yes. Update the feed. update(fs); } } // No, this location is not supported. Do not modify the // activation count. if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format("%s subscriber %d, feed %d: %s (%s) feed state=%s, activation count=%d (%s), update?=%b -> %d.", mEClient.location(), mEClient.clientId(), mFeedId, key(), loc, fs, mActivationCount, mScope, updateFlag, retval)); } return (retval); } // end of updateActivation(ClientLocation) /** * Updates the subscriber feed state. An {@code UP} feed * state means the publisher may start posting notification * messages to this feed as long as the publisher's feed * state is also {@code UP}. Issues a callback to the * client's feed state callback method. * @param feedState latest publisher state. */ @Override /* package */ void update(final EFeedState feedState) { if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format("%s subscriber %d, feed %d: update feed state=%s.", mEClient.location(), mEClient.clientId(), mFeedId, feedState)); } mFeedState = feedState; // Note: the caller acquired the client dispatch before // calling this method. mEClient.dispatch( new StatusTask<>(feedState, this, mStatusCallback)); return; } // end of update(EFeedState) /** * If the advertisement is in place, then retracts it. */ @Override protected void inactivate() { // Retract the subscription in case it is in place. unsubscribe(); return; } // end of inactivate() // // end of Abstract Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // IESubscribeFeed Interface Implementation. // /** * 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. * @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. * @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) /** * Activates this notification subscription. The caller will * be asynchronously informed of the current feed state via * the * {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed) feed stateupdate callback method}. *

* Nothing is done if already subscribed. *

* @throws IllegalStateException * if this feed is closed or if the client did not override * {@link ESubscriber} methods nor put the required callbacks * in place. * * @see #unsubscribe() * @see EFeed#close() */ @Override public void subscribe() { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (!mInPlace) { // If the feed state and/or notify callbacks are // not set, then set them to their defaults. if (mStatusCallback == null) { // Did the subscriber override feedStatus? if (!isOverridden(FEED_STATUS_METHOD, EFeedState.class, IESubscribeFeed.class)) { // No? Gotta do one or the other. throw ( new IllegalStateException( FEED_STATUS_METHOD + " not overridden and statusCallback not set")); } // Yes. Use the override method. mStatusCallback = ((ESubscriber) mEClient.target())::feedStatus; } if (mNotifyCallback == null) { // Did the subscriber override notify? if (!isOverridden(NOTIFY_METHOD, ENotificationMessage.class, IESubscribeFeed.class)) { // Nope. Not much point in putting this // subscription is place. throw ( new IllegalStateException( NOTIFY_METHOD + " not overridden and notifyCallback not set")); } if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s subscriber %d, feed %d: subscribing to %s (%s).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope)); } mNotifyCallback = ((ESubscriber) mEClient.target())::notify; } ((ENotifySubject) mSubject).subscribe(this); // The subscription is now in place. mInPlace = true; } return; } // end of subscribe() /** * De-activates this subscriber feed. Does nothing if this * feed is not currently subscribed. *

* Note that the client may still receive notification * messages which were posted concurrently as this * unsubscribe. *

* * @see subscribe * @see EFeed#close() */ @Override public void unsubscribe() { // Is the feed subscribed? if (mInPlace) { // Yes. Well, unsubscribe it. if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s subscriber %d, feed %d: unsubscribing from %s (%s).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope)); } ((ENotifySubject) mSubject).unsubscribe(this); // This feed is no longer subscribed ... mInPlace = false; // ... which means there are no more publishers. mActivationCount = 0; mFeedState = EFeedState.UNKNOWN; } return; } // end of unsubscribe() // // end of IESubscribeFeed Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Dispatches {@code msg} to the client if the message passes * the subscription condition. * @param msg forward this message to the client if this * message is acceptable. */ /* package */ void notify(final ENotificationMessage msg) { // Is the subscription still in place? if (mInPlace) { // Yes. Post this message to the client task queue. // The notify task will check the subscription // condition prior to forwarding this message. // Note: the caller acquired the client dispatch // before calling this method. mEClient.dispatch( new NotifyTask( msg, mCondition, this, mNotifyCallback)); } return; } // end of notify(ENotificationMessage) // // end of Set Methods. //----------------------------------------------------------- /** * Returns a notification subscriber feed for the specified * notification message class and subject. The client calls * {@link #subscribe} on the returned feed in order to * start receiving the specified notification messages. * @param client the application object subscribing to the * notification. May not be {@code null}. * @param key the notification message class and subject. * May not be {@code null} and must reference a notification * message class. * @param scope whether this subscription feed matches local * publishers only, remote publishers only, or both. * @param condition accept notification messages only if the * messages passes this condition. May be {@code null}. If * {@code null}, then the * {@link #NO_CONDITION default condition} which accepts all * messages is used. * @return the newly created, unplaced, subscribe feed. Must * call {@link #subscribe()} in order to receive messages. * @throws NullPointerException * if {@code client}, {@code key}, or {@code scope} is * {@code null}. * @throws IllegalArgumentException * if any of the given parameters is invalid. * * @see #subscribe() * @see EFeed#close() */ @SuppressWarnings ("unchecked") public static ESubscribeFeed open(final ESubscriber client, final EMessageKey key, final FeedScope scope, ECondition condition) { // Validate the parameters. // Is the message key for a notification? if (!(Objects.requireNonNull(key, "key is null")).isNotification()) { throw ( new IllegalArgumentException( String.format( "%s is not a notification message", key))); } return (open(Objects.requireNonNull(client, "client is null"), key, Objects.requireNonNull(scope, "scope is null"), condition, ClientLocation.LOCAL, false)); } // end of open(ESubscriber,EMessageKey,FeedScope,ECondition) /** * Returns a notification subscriber feed for the specified * notification message class and subject. The client calls * {@link #subscribe} on the returned feed in order to * start receiving the specified notification messages. *

* This method does not parameter validation since this is * a {@code package private} method. *

* @param client the application object subscribing to the * notification. May not be {@code null}. * @param key the notification message class and subject. * May not be {@code null} and must reference a notification * message class. * @param scope whether this subscription feed matches local * publishers only, remote publishers only, or both. * @param cond accept notification messages only if the * messages passes this condition. May be {@code null}. If * {@code null}, then the * {@link #NO_CONDITION default condition} which accepts all * messages is used. * @param l {@code client} location. * @param isMulti {@code true} if this is part of a multiple * key feed. If {@code true}, this feed is not added to the * client feed list. * @return the newly created, unplaced, subscribe feed. Must * call {@link #subscribe()} in order to receive messages. */ /* package */ static ESubscribeFeed open(final ESubscriber client, final EMessageKey key, final FeedScope scope, final ECondition cond, final ClientLocation l, final boolean isMulti) { // If a null condition was passed in, then use the // default NO_CONDITION. final ECondition subCondition = (cond == null ? NO_CONDITION : cond); final EClient eClient; final ENotifySubject subject; final ESubscribeFeed retval; // Find ore open the eBus client wrapping client. eClient = EClient.findOrCreateClient(client, l); // Lastly, find or open the notification subject. subject = ENotifySubject.findOrCreate(key); // Clear to create the subscribe feed. retval = new ESubscribeFeed(eClient, scope, subCondition, subject); // Let the client know it is being referenced by another // feed - but only if this is not part of a multiple // key feed. In that case, the multiple key feed is // added to the client. if (!isMulti) { eClient.addFeed(retval); } if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "%s subscriber %d, feed %d: opened %s (%s).", eClient.location(), eClient.clientId(), retval.feedId(), key, scope)); } return (retval); } // end of open(...) } // end of class ESubscribeFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy