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

net.sf.eBus.client.ESubscribeFeed Maven / Gradle / Ivy

The newest version!
//
// Copyright 2015, 2016, 2018, 2019 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package net.sf.eBus.client;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.sf.eBus.client.EClient.ClientLocation;
import static net.sf.eBus.client.EFeed.NOTIFY_METHOD;
import net.sf.eBus.logging.AsyncLoggerFactory;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.util.Validator;
import org.slf4j.Logger;

/**
 * {@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: * Use {@link ESubscribeFeed.Builder} to create a subscribe feed * for a given {@code ESubscriber} instance and * {@link EMessageKey type+topic message key}. The condition is * optional and defaults to {@code null}. If provided, then only * notification messages satisfying the condition are forwarded * to the subscriber. *

* Use {@link Builder#statusCallback(FeedStatusCallback)} and * {@link Builder#notifyCallback(NotifyCallback)} to set Java * lambda expressions used in place of {@link ESubscriber} * interface methods. *

*

*

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

*

* Step 4: 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 5: Once the feed state is up, wait for * {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed) notification messages} * to arrive. *

*

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

*

Example use of 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 subscriber has no associated ECondition and uses ESubscriber interface method overrides.
            mFeed = (ESubscribeFeed.builder()).target(this)
                                              .messageKey(mKey)
                                              .scope(mScope)
                                              .build();

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

    Step 4: 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 5: Wait for notifications to arrive.
    @Override public void notify(final ENotificationMessage msg, final IESubscribeFeed feed) {
        // Notification handling code here.
    }

    @Override public void shutdown() {
    Step 6: 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 = AsyncLoggerFactory.getLogger(ESubscribeFeed.class); //----------------------------------------------------------- // 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 final FeedStatusCallback mStatusCallback; /** * Notification message callback. If not explicitly set by * client, then defaults to * {@link ESubscriber#notify(ENotificationMessage, ESubscribeFeed)}. */ private final NotifyCallback mNotifyCallback; /** * If this subscription is for the latest notification * message, then create the task used to deliver the * latest notification once and reuse for all message * delivery. */ private final @Nullable LatestNotifyTask mLatestTask; /** * Use to create a {@link NotifyTask} instance used to * forward message to subscriber. */ private final Function mTaskCreator; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // private ESubscribeFeed(final Builder builder) { super (builder); mCondition = builder.mCondition; mStatusCallback = builder.mStatusCallback; mNotifyCallback = builder.mNotifyCallback; if (builder.mLatestOnly) { mLatestTask = new LatestNotifyTask( mCondition, this, mNotifyCallback); mTaskCreator = this::createLatestTask; } else { mLatestTask = null; mTaskCreator = this::createTask; } } // end of ESubscribeFeed(Builder) // // 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. sLogger.trace( "{} subscriber {}, feed {}: {} ({}) feed state={}, activation count={} ({}), update?={} -> {}.", 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) { sLogger.trace( "{} subscriber {}, feed {}: update feed state={}.", 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)); } // 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(); } // end of inactivate() // // end of Abstract Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // IESubscribeFeed Interface Implementation. // /** * 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) { ((ENotifySubject) mSubject).subscribe(this); // The subscription is now in place. mInPlace = true; } } // 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. sLogger.debug( "{} subscriber {}, feed {}: unsubscribing from {} ({}).", 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; } } // 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) { final NotifyTask task; // Is the subscription still in place? // Is this task in place? if (mInPlace && (task = mTaskCreator.apply(msg)) != null) { // 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(task); } } // end of notify(ENotificationMessage) // // end of Set Methods. //----------------------------------------------------------- /** * Returns a new {@code ESubscribeFeed} builder instance. * This instance should be used to build a single * notification subscribe feed instance and not used to * create multiple such feeds. * @return new notification subscribe feed builder. */ public static Builder builder() { return (new Builder()); } // end of builder() /** * Verifies that given parameters are not set to {@code null} * and that {@code key} references a notification message and * that the message scope and feed scope are in agreement. * This method is called for effect only. * @param client subscriber client. * @param key notification message code. * @param scope feed scope. */ protected static void validateOpenParams(final ESubscriber client, final EMessageKey key, final FeedScope scope) { // Are the parameters non-null references? Objects.requireNonNull(client, "client is null"); Objects.requireNonNull(key, "key is null"); Objects.requireNonNull(scope, "scope is null"); // Is the message key for a notification? if (!key.isNotification()) { throw ( new IllegalArgumentException( String.format( "%s is not a notification message", key))); } // Are the feed scope and message scope in agreement? checkScopes(key, scope); } // end of validateOpenParams(...) /** * Returns {@code NotifyTask} containing given notification * message. * @param msg wrap this notification message in a new * notify task. * @return new {@code NotifyTask} instance. */ private NotifyTask createTask(final ENotificationMessage msg) { return ( new NotifyTask( msg, mCondition, this, mNotifyCallback)); } // end of createTask(ENotificationMessage) /** * Returns {@link #mLatestTask} instance if this * task is not already posted to the subscriber's dispatch * queue; otherwise this queued task's notification message * is updated resulting in the undelivered notification being * dropped. * @param msg place message in latest task. * @return latest task instance if not on queue and * {@code null} if on queue. */ private @Nullable NotifyTask createLatestTask(final ENotificationMessage msg) { return (mLatestTask.latest(msg) ? mLatestTask : null); } // end of createLatestTask(ENotificationMessage) //--------------------------------------------------------------- // Inner classes. // /** * {@code ESubscribeFeed.Builder} is now the preferred * mechanism for creating a {@code ESubscribeFeed} instance. * A {@code Builder} instance is acquired from * {@link ESubscribeFeed#builder()}. The following example * shows how to create a {@code ESubscribeFeed} instance * using a {@code Builder}. The code assumes that the class * implements {@code ESubscriber} interface * {@code feedStatus} method but uses a separate method for * notification message callback. *
@Override public void startup() {
    final EMessageKey key = new EMessageKey(CatalogUpdate.class, subject);
    final ESubscribeFeed feed = (ESubscribeFeed.builder()).target(this)
                                                          .messageKey(key)
                                                          .scope(EFeed.FeedScope.REMOTE_ONLY)
                                                          .condition(m → ((CatalogUpdate) m).category == Category.APPLIANCES)
                                                          // Call .statusCallback(lambda expression) to replace feedStatus method
                                                          .notifyCallback(this::catalogUpdate)
                                                          .build();
    ...
}
*

* eBus release 7.3.0 introduced {@link #latestOnly(boolean)} * property which allows a subscriber to receive only the * latest notification in case publisher(s) post * notifications faster than the subscriber can process them * and the subscriber must work with the latest * notification for correct processing. *

* * @see #builder() */ public static final class Builder extends ENotifyFeed.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Set to {@code true} if subscriber wishes to receive * latest only the latest notification for the configured * message key. Defaults to {@code false} which means all * notifications will be delivered to the subscriber. */ private boolean mLatestOnly; /** * Use this condition to check if the notification message * should be forwarded to subscriber. Default settings is * {@link #NO_CONDITION}. */ private 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 builder. */ private Builder() { super (FeedType.SUBSCRIBE_FEED, ESubscribeFeed.class); mLatestOnly = false; mCondition = NO_CONDITION; } // end of Builder() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Overrides. // /** * Checks if feed status and notify callbacks are either * set explicitly or the {@code ESubscriber} methods are * overridden. * @param problems place invalid configuration settings * in this problems list. * @return {@code problems} to allow for method chaining. */ @Override protected Validator validate(Validator problems) { // If the status and notify callbacks are not // set, then use the interface methods if defined. // Did the subscriber override feedStatus? if (mStatusCallback == null && mTarget != null && isOverridden(FEED_STATUS_METHOD, EFeedState.class, IESubscribeFeed.class)) { // Yes. Use the override method. mStatusCallback = ((ESubscriber) mTarget)::feedStatus; } // Did the subscriber override notify? if (mNotifyCallback == null && mTarget != null && isOverridden(NOTIFY_METHOD, ENotificationMessage.class, IESubscribeFeed.class)) { // Yes. Use the override method. mNotifyCallback = ((ESubscriber) mTarget)::notify; } return (super.validate(problems) .requireNotNull(mStatusCallback, "statusCallback") .requireNotNull(mNotifyCallback, "notifyCallback")); } // end of validate(Validator) /** * Returns a new {@code ESubscribeFeed} instance based on * {@code this Builder}'s configuration. * @return new notification subscribe feed. */ @Override protected ESubscribeFeed buildImpl() { final ESubscribeFeed retval = new ESubscribeFeed(this); // 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 (!mIsMultiFlag) { mEClient.addFeed(retval); } sLogger.debug( "{} subscriber {}, feed {}: opened {} ({}).", mLocation, mEClient.clientId(), retval.feedId(), mKey, mScope); return (retval); } // end of buildImpl() /** * Returns {@code this} reference. * @return {@code this} reference. */ @Override protected Builder self() { return (this); } // end of self() // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets latest-only notification value to given flag. * This capability should be used when publisher(s) may * post notifications faster than a subscriber can * process them and the subscriber does not need * to receive all messages but the latest notification. * In other words, the subscriber's correct notification * processing depends on keeping up with the latest * update and not on processing all notifications. *

* Default setting is for subscriber to receive all * notifications - that is {@code false}. *

* @param flag if {@code true} then only most recent * notification for configured message key is delivered * to subscriber. * @return {@code this Builder} instance. */ public Builder latestOnly(final boolean flag) { mLatestOnly = flag; return (this); } // end of latestOnly(boolean) /** * Sets subscription condition to the given value. May * be {@code null} which results in a * {@link #NO_CONDITION} condition. *

* An example using this method is: *

* {@code condition(m -> ((CatalogUpdate) m).category == Category.APPLIANCES)} * @param condition subscription condition. * @return {@code this Builder} instance. */ public Builder condition(final ECondition condition) { if (condition == null) { mCondition = NO_CONDITION; } else { mCondition = condition; } return (this); } // end of condition(ECondition) /** * Puts the subscribe feed status callback in place. If * {@code cb} is not {@code null}, subscribe 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}, * then updates are posted to the * {@link ESubscriber#feedStatus(EFeedState, IESubscribeFeed)} * override. *

* An example using this method is: *

*
statusCallback(
    (fs, f) →
    {
        if (fs == EFeedState.DOWN) {
            // Clean up in-progress work.
        }
    }
* @param cb subscribe status update callback. May be * {@code null}. * @return {@code this Builder} instance. */ public Builder statusCallback(final FeedStatusCallback cb) { mStatusCallback = cb; return (this); } // 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. *

* An example showing how to use this method is: * {@code notifyCallback(this::locationUpdate)} *

* @param notification message subclass passed into * callback. * @param cb pass notification messages back to target * via this callback. * @return {@code this Builder} instance. */ public Builder notifyCallback(final NotifyCallback cb) { mNotifyCallback = cb; return (this); } // end of notifyCallback() // // end of Set Methods. //------------------------------------------------------- } // end of class Builder /** * Task used to delivery latest notification message to * subscriber. */ private final class LatestNotifyTask extends NotifyTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Store latest notification message here and retrieve * when notify callback is performed. */ private final AtomicReference mLatestMessage; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a notify callback task which forwards latest * notification message to subscriber. * @param condition subscription condition. * @param feed message is from this feed. * @param cb notification message callback. */ public LatestNotifyTask(final ECondition condition, final IESubscribeFeed feed, final NotifyCallback cb) { super (condition, feed, cb); mLatestMessage = new AtomicReference<>(); } // end of LatestNotifyTask(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // NotifyTask Method Overrides. // /** * Returns latest notification message stored in this * task. * @return latest notification message. */ @Override protected @Nullable ENotificationMessage message() { return (mLatestMessage.getAndSet(null)); } // end of message() // // end of NotifyTask Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // private boolean latest(final ENotificationMessage message) { final ENotificationMessage previous = mLatestMessage.getAndSet(message); return (previous == null); } // end of latest(ENotificationMessage) // // end of Set Methods. //------------------------------------------------------- } // end of class LatestNotifyTask } // end of class ESubscribeFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy