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

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

The newest version!
//
// Copyright 2017, 2023 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 static net.sf.eBus.client.EFeed.NOTIFY_METHOD;
import static net.sf.eBus.client.EFeed.NO_CONDITION;
import static net.sf.eBus.client.ESubscribeFeed.FEED_STATUS_METHOD;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.util.Validator;

/**
 * This feeds acts as a proxy for handling multiple
 * {@link ESubscribeFeed}s on behalf of a {@link ESubscriber}
 * client. A subscriber opens a multi-subject subscribe feed for
 * a specified notification message class and zero or more
 * message subjects. 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-subject 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-subject feed is open, new subordinate * subscribe feeds may be {@link #addFeed(String) added to} or * {@link #closeFeed(String) removed from} the multi-subject feed. * Newly added subordinate feeds are configured and put into the * same state as the existing subordinate feeds. *

*

Example use of EMultiSubscribeFeed

*
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;
import net.sf.eBus.util.regex.Pattern;

Step 1: Implement the ESubscriber interface.
public class CatalogSubscriber implements ESubscriber {
    // Select subjects matching this query pattern.
    private final Pattern mQuery;

    // Subscribe to this feed scope.
    private final FeedScope mScope;

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

    public CatalogSubscriber(final Pattern query, final FeedScope scope) {
        mQuery = query;
        mScope = scope;
    }

    @Override public void startup() {
        try {
            Step 2: Open the ESubscribe feed.
            // This subscriber has no associated ECondition and uses ESubscriber interface method overrides.
            mFeed = (EMultiSubscribeFeed.builder()).target(this)
                                                   .scope(mScope)
                                                   .messageClass(CatalogUpdate.class)
                                                   .query(mQuery)
                                                   .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 EMultiPublishFeed * * @author Charles W. Rapp */ public final class EMultiSubscribeFeed extends EMultiFeed implements IESubscribeFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Locals. // /** * Use this condition to check if received message should be * forwarded to client. This condition is applied to all * subordinate feeds which use conditions. This data member * is {@code null} if the subordinate feed does not support * message condition. */ protected final ECondition mCondition; /** * Feed status callback. If not explicitly set by client, * then defaults to * {@link ESubscriber#feedStatus(EFeedState, ESubscribeFeed)}. * Applied to all subordinate subscribe feeds. */ private final 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 final NotifyCallback mNotifyCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new multi-subscribe feed instance based on the * validated builder settings. * @param builder contains validated feed configuration. */ private EMultiSubscribeFeed(final Builder builder) { super (builder); mCondition = builder.mCondition; mStatusCallback = builder.mStatusCallback; mNotifyCallback = builder.mNotifyCallback; } // end of EMultiSubscribeFeed(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // IESubscribeFeed Interface Implementations. // /** * 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) { sLogger.debug( "{} multi-subject subscriber {}: subscribing ({}).", mEClient.location(), mEClient.clientId(), mScope); // Subscribe each subordinate feed. mFeeds.values() .stream() .forEachOrdered(ESubscribeFeed::subscribe); // This feed is now advertised. mInPlace = true; // If this multi-subject feed is query based, then // listen for subject updates. if (mQuery != null) { ESubject.addListener(this); } } } // end of subscribe() /** * Retracts this multi-subject subscribe feed by un-subscribing * each subordinate subscribe feed. Does nothing if this * feed is not currently subscribed. * @throws IllegalStateException * if this multi-subject subscribe feed is closed. * * @see #subscribe() * @see #close() */ @Override public void unsubscribe() { if (!mIsActive.get()) { throw (new IllegalStateException(FEED_IS_INACTIVE)); } if (mInPlace) { sLogger.debug( "{} multi-subject subscriber {}: unsubscribing ({}).", mEClient.location(), mEClient.clientId(), mScope); // Unadvertise each subordinate feed. mFeeds.values() .stream() .forEachOrdered(ESubscribeFeed::unsubscribe); // This feed is no longer subscribed. mInPlace = false; // Stop listening for subject updates. ESubject.removeListener(this); } } // 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 ESubscribeFeed.Builder builder = ESubscribeFeed.builder(); return (builder.target((ESubscriber) mEClient.target()) .location(location()) .messageKey(key) .scope(mScope) .statusCallback(mStatusCallback) .notifyCallback(mNotifyCallback) .build()); } // end of createFeed(EMessageKey) /** * Sets the callbacks and subscribes the {@code feed}. * @param feed subscribe this feed. */ @Override protected void putFeedInPlace(final ESubscribeFeed feed) { feed.subscribe(); } // end of putFeedInPlace(ESubscribeFeed) // // end of Abstract Method Implementations. //----------------------------------------------------------- /** * Returns a new multi-subscribe feed builder. It is * recommended that a new {@code Builder} instance be used * for each {@code EMultiSubscribeFeed} creation. * @return new multi-subscribe feed builder instance. */ public static Builder builder() { return (new Builder()); } // end of builder() //--------------------------------------------------------------- // Inner classes. // /** * {@code EMultiSubscribeFeed.Builder} is the mechanism for * creating an {@code EMultiSubscribeFeed} instance. A * {@code Builder} instance is acquired from * {@link EMultiSubscribeFeed#builder()}. The following * example shows how to create an {@code EMultiSubscribeFeed} * instance using a {@code Builder}. The code assumes that * the target class implements {@code ESubscriber} interface * methods. *
@Overricde public void startup() {
    final EMultiSubscribeFeed feed = (EMultiSubscribeFeed.builder()).target(this)
                                                                    .messageClass(CatalogUpdate.class)
                                                                    .scope(EFeed.FeedScope.REMOTE_ONLY)
                                                                    .subjects(mSubjectsList) // msSubjectsList is List<String> subject list
                                                                    // Call .statusCallback(lambda expression) and
                                                                    // .notifyCallback(lambda expression) to replace feedStatus, notify methods
                                                                    .build();
    ...
}
*/ public static final class Builder extends EMultiFeed.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Apply this condition to all incoming messages. Only * those messages satisfying the condition are forwarded * to the target. Defaults to {@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. // private Builder() { super (EMultiSubscribeFeed.class, ENotificationMessage.class); mCondition = NO_CONDITION; } // end of Builder() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Overrides. // @Override protected Validator validate(final 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, FEED_STATUS_METHOD + " not overridden and statusCallback not set") .requireNotNull(mNotifyCallback, NOTIFY_METHOD + " not overriden and notifyCallback not set")); } // end of validate(Validator) @Override protected ESubscribeFeed createFeed(EMessageKey key) { final ESubscribeFeed.Builder builder = ESubscribeFeed.builder(); return (builder.target((ESubscriber) mTarget) .location(mLocation) .messageKey(key) .scope(mScope) .statusCallback(mStatusCallback) .notifyCallback(mNotifyCallback) .build()); } @Override protected EMultiSubscribeFeed buildImpl() { return (new EMultiSubscribeFeed(this)); } // end of buildImpl() @Override protected Builder self() { return (this); } // end of self() // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * 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(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 } // end of class EMultiSubscribeFeed




© 2015 - 2024 Weber Informatics LLC | Privacy Policy