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

net.sf.eBus.client.EMultiReplyFeed 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.EFeed.FeedScope;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ERequestMessage;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.util.regex.Pattern;

/**
 * This feed allows an {@link EReplier} to open one feed for a
 * given request message class and multiple message subjects. It
 * acts as a proxy between the replier and the individual,
 * subordinate feeds. The replier interacts solely with the
 * multi-key reply feed and is unable to access the underlying
 * {@code EReplyFeed}s. The replier 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 EReplyFeed}s. But the
 * subordinate feeds issue
 * {@link EReplier#request(EReplyFeed.ERequest)}
 * callbacks to the {@code EReplier} registered with the
 * multi-key feed. The multi-key feed does not callback to the
 * replier 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 reply * message class and subject list to * {@link #open(EReplier, Class, List, EFeed.FeedScope, ECondition) open} * or * a notification message class and regular express query to * {@link #open(EReplier, Class, Pattern, EFeed.FeedScope, ECondition)}. * 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 reply 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 EMultiReplyFeed extends EMultiFeed implements IEReplyFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Statics. // /** * Lambda expression used to create a new subordinate reply * feed. */ private static final SubordinateFeedFactory sSubFactory = (cl, key, sc, cond, loc) -> { final MessageType mt = (MessageType) DataType.findType( key.messageClass()); return (EReplyFeed.open(cl, key, sc, cond, loc, mt, true)); }; /** * Lambda expression used to create a new multi-key reply * feed. */ private static final MultiFeedFactory sMultiFactory = (cl, mc, sc, cond, feeds) -> { final MessageType mt = (MessageType) DataType.findType(mc); return ( new EMultiReplyFeed( cl, mc, sc, cond, feeds, mt)); }; //----------------------------------------------------------- // Locals. // /** * Tracks the replier's ability to handle request messages. * for this feed. {@link #mFeedState} tracks whether there * are any requestor to this feed. */ private EFeedState mReplyState; /** * The request message data type. Used to determine if reply * messages belong to the * {@link net.sf.eBus.messages.EReplyInfo} allowed message * types. */ private final MessageType mDataType; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then defaults * to * {@link EReplier#request(EReplyFeed.ERequest)}. */ private RequestCallback mRequestCallback; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then defaults * to * {@link EReplier#cancelRequest(EReplyFeed.ERequest)}. */ private CancelRequestCallback mCancelCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new instance of EMultiReplyFeed. */ private EMultiReplyFeed(final EClient client, final Class mc, final FeedScope scope, final ECondition condition, final Map feeds, final MessageType dataType) { super (client, mc, scope, condition, feeds); mDataType = dataType; mReplyState = EFeedState.UNKNOWN; mRequestCallback = null; mCancelCallback = null; } // end of EMultiReplyFeed(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // IEReplyFeed Interface Implementations. // /** * Returns {@code true} if this reply feed is both open and * advertised; otherwise, returns {@code false}. * @return {@code true} if this reply feed is open and * advertised. */ @Override public boolean isAdvertised() { return (mIsActive.get() && mInPlace); } // end of isAdvertised() /** * Puts the new request callback in place. If {@code cb} is * not {@code null}, requests will be passed to {@code cb} * rather than * {@link EReplier#request(EReplyFeed.ERequest)}. A * {@code null cb} means that requests are passed to the * {@link EReplier#request(EReplyFeed.ERequest)} override. * @param cb the request callback. May be {@code null}. * @throws IllegalStateException * if this feed is either closed or advertised. */ @Override public void requestCallback(final RequestCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } else if (mInPlace) { throw ( new IllegalStateException( "advertisement in place")); } mRequestCallback = cb; return; } // end of requestCallback(RequestCallback) /** * Puts the cancel request callback in place. If {@code cb} * is not {@code null}, requests will be passed to {@code cb} * rather than * {@link EReplier#cancelRequest(EReplyFeed.ERequest)}. A * {@code null cb} means that cancellations are passed to the * {@link EReplier#cancelRequest(EReplyFeed.ERequest)} * override. * @param cb the cancel request callback. May be * {@code null}. * @throws IllegalStateException * if this feed is either closed or advertised. */ @Override public void cancelRequestCallback(final CancelRequestCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } else if (mInPlace) { throw ( new IllegalStateException( "advertisement in place")); } mCancelCallback = cb; return; } // end of cancelRequestCallback(CancelRequestCallback) /** * Advertises each subordinate {@link EReplyFeed}. If this * feed is currently advertised, then does nothing. If the * cancel request and request callbacks were previously set * for this feed, then these callbacks are set in the * subordinate feeds prior to advertising them. * @throws IllegalStateException * if this feed is closed or the client did not override * {@link EReplier} methods nor put the required callbacks * 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 replier %d: advertising (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Advertise each subordinate feed. mFeeds.values() .stream() .map( feed -> { feed.requestCallback(mRequestCallback); feed.cancelRequestCallback( mCancelCallback); return feed; }) .forEachOrdered(EReplyFeed::advertise); // This feed is now advertised. mInPlace = true; } return; } // end of advertise() /** * Retracts this multi-key replier feed by un-advertising * each subordinate reply feed. Does nothing if this * feed is not currently advertised. * @throws IllegalStateException * if this multi-key reply 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 replier %d: unadvertising (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Unadvertise each subordinate feed. mFeeds.values() .stream() .forEachOrdered(EReplyFeed::unadvertise); // This feed is no longer advertised. mReplyState = EFeedState.UNKNOWN; mInPlace = false; } return ; } // end of unadvertise() /** * Updates the reply 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 reply feed states are updated * as well. *

* The reply 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 reply 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 != mReplyState) { // Yes. Apply the update. mReplyState = update; if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s multi-key replier %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) // // end of IEReplyFeed Interface Implementations. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Implementations. // /** * Returns a newly minted subordinate reply feed for the * given key. * @param key create feed for this key. * @return a subordinate reply feed. */ @Override protected EReplyFeed createFeed(EMessageKey key) { final EReplier replier = (EReplier) mEClient.target(); return (EReplyFeed.open(replier, key, mScope, mCondition, EClient.ClientLocation.LOCAL, mDataType, true)); } // end of createFeed(EMessageKey) /** * Sets the feed status callback, advertises {@code feed}, * and updates the reply feed state. The reply feed state * is taken from the most recent * {@link #updateFeedState(EFeedState)} setting. If the * reply feed state has never been updated, then the state * is {@link EFeedState#UNKNOWN}. * @param feed advertise this feed. */ @Override protected void putFeedInPlace(final EReplyFeed feed) { feed.cancelRequestCallback(mCancelCallback); feed.requestCallback(mRequestCallback); feed.advertise(); feed.updateFeedState(mReplyState); return; } // end of putFeedInPlace(EReplyFeed) // // end of Abstract Method Implementations. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns the reply state which specifies whether this * multi-key reply feed (and its subordinate feeds) are * ready to handle requests or not. An update reply state * does not mean that the replier will receive requests but * only that the replier is capable of handling requests at * this time. * @return current reply state. * * @see #feedState(String) */ public EFeedState replyState() { return (mReplyState); } // end of replyState() // // end of Get Methods. //----------------------------------------------------------- /** * Returns an open reply feed for multiple request message * keys. Once opened, the caller can (optionally) set the * cancel and request callbacks and advertise the feed just * like {@link EReplyFeed}. *

* Note: {@code client} receives callbacks * for each subordinate {@code EReplyFeed} 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 callbacks for each of the subordinate * reply feeds. *

*

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

* @param client the application object handling request * feeds. * @param mc message class for all subordinate feeds. * @param subjects list of request message class and * subject. 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. * @param condition accept request messages only if the * messages passes this condition. May be {@code null}. If * {@code null}, then the * {@link ERequestFeed#NO_CONDITION default condition} * which accepts all messages is used. * @return a new multiple key reply feed for the given * application object and request message keys. * @throws NullPointerException * if any of the required arguments is {@code null}. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. * * @see #open(EReplier, Class, Pattern, EFeed.FeedScope, ECondition) * @see #cancelRequestCallback(CancelRequestCallback) * @see #requestCallback(RequestCallback) * @see #advertise() * @see #unadvertise() * @see EMultiFeed#close() */ public static EMultiReplyFeed open(final EReplier client, final Class mc, final List subjects, final FeedScope scope, final ECondition condition) { return (openList(client, mc, subjects, scope, condition, sSubFactory, sMultiFactory)); } // end of open(EReplier, List<>, FeedScope, ECondition) /** * Returns an open reply feed for a request message class * and multiple subjects. Once opened, the caller can * (optionally) set the status callback and advertise the * feed just like {@link EReplyFeed}. *

* The subordinate reply feeds are selected based on the * given request 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 reply feed will have no initial * subordinate feeds. If that is the case, new reply 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 EReplyFeed} as if it opened * those feeds directly. If {@code mc} and {@code query} * matches a large number of request message keys, then * {@code client} must be prepared for a large number of * callbacks. *

* @param client the application object replying to the * request message class and subjects. * @param mc the message key query is for this request * 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 EReplyFeed#NO_CONDITION default condition} * which accepts all messages is used. * @return a new multiple key publisher feed for the given * application object and request 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(EReplier, Class, List, EFeed.FeedScope, ECondition) * @see #cancelRequestCallback(CancelRequestCallback) * @see #requestCallback(RequestCallback) * @see #advertise() * @see #unadvertise() */ public static EMultiReplyFeed open(final EReplier client, final Class mc, final Pattern query, final FeedScope scope, final ECondition condition) { return (openQuery(client, mc, query, scope, condition, sSubFactory, sMultiFactory)); } // end of open(EReplier,Class,Pattern,boolean,FeedScope) } // end of class EMultiReplyFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy