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

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

//
// Copyright 2017 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.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.client.ERequestFeed.ERequest;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.EReplyMessage;
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 acts as a proxy for handling multiple
 * {@link ERequestFeed}s on behalf of a {@link ERequestor}
 * client. A replier opens a multi-key reply feed for a specified
 * request message class and zero or more message subjects. These
 * 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 search the message key dictionary for all
 * matching subjects. The matching subjects are used to create
 * the initial subordinate {@code ERequestFeed}s.
 * 

* The multi-key request feed coordinates the subordinate request * feeds so they are given the same configuration and are in the * same state (open, subscribed, un-subscribed, closed). *

*

* While the multi-key feed is open, new subordinate request * feeds may be {@link #addFeed(String) added to} or * {@link #closeFeed(String) removed from} the multi-key feed. * Newly added subordinate feeds are configured and put into the * same state as the existing subordinate feeds. *

* * @author Charles W. Rapp */ public final class EMultiRequestFeed extends EMultiFeed implements IERequestFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Statics. // /** * Lambda expression used to create a new subordinate reply * feed. */ private static final SubordinateFeedFactory sSubFactory = (cl, key, sc, cond, loc) -> ERequestFeed.open(cl, key, sc, loc, 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); final Map, ReplyCallback> cbs = ERequestFeed.createReplyCallbacks(mt); return (new EMultiRequestFeed(cl, mc, sc, feeds, cbs)); }; //----------------------------------------------------------- // Locals. // /** * Contains the functional interface callback for feed * status updates. If not explicitly set by client, then * defaults to * {@link ERequestor#feedStatus(EFeedState, ERequestFeed)}. * This callback is applied to all subordinate request feeds. */ private FeedStatusCallback mStatusCallback; /** * Maps the reply message key to the functional interface * callback for that reply. If a reply message is not * explicitly set by the client, then defaults to * {@link ERequestor#reply(int, EReplyMessage, ERequestFeed.ERequest)}. * This callback is applied to all subordinate request feeds. */ private final Map, ReplyCallback> mReplyCallbacks; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new instance of EMultiRequestFeed. * @param cbs reply callback map with all message keys * set to {@code null} callbacks. */ private EMultiRequestFeed(final EClient client, final Class mc, final FeedScope scope, final Map feeds, final Map, ReplyCallback> cbs) { super (client, mc, scope, null, feeds); mStatusCallback = null; mReplyCallbacks = cbs; } // end of EMultiRequestFeed(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // IERequestFeed Interface Implementations. // /** * 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 ERequestor#feedStatus(EFeedState, IERequestFeed)}. * A {@code null cb} results in feed status updates posted to * the * {@link ERequestor#feedStatus(EFeedState, IERequestFeed)} * override. * @param cb 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; } // end of statusCallback(FeedStatusCallback<>) /** * Puts the reply message callback in place * for all reply types. If {@code cb} is * not {@code null}, replies will be passed to {@code cb} * rather than * {@link ERequestor#reply(int, EReplyMessage, ERequestFeed.ERequest)}. * A {@code null cb} results in replies posted to the * {@code ERequestor.reply(int, EReplyMessage, ERequestFeed.ERequest)} * override. *

* Note that this method call overrides all previous calls * to {@link #replyCallback(Class, ReplyCallback)}. If * the goal is to use a generic callback for all replies * except one specific message, then use this method to put * the generic callback in place first and then use * {@code replyCallback(EMessageKey, ReplyCallback)}. *

* @param cb reply message callback. May be {@code null}. * @throws IllegalStateException * if this feed is either closed or subscribed. * * @see #replyCallback(Class, ReplyCallback) */ @Override public void replyCallback(final ReplyCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { throw ( new IllegalStateException( "subscription in place")); } mReplyCallbacks.entrySet() .forEach(entry -> entry.setValue(cb)); } // end of replyCallback(ReplyCallback) /** * Sets the callback for a specific reply message class. If * {@code cb} is not {@code null}, replies will be passed to * {@code cb} rather than * {@link ERequestor#reply(int, EReplyMessage, ERequestFeed.ERequest)}. * A {@code cb} results in replies posted to the * {@code ERequestor.reply(int, EReplyMessage, ERequestFeed.ERequest)} * override. *

* If the goal is to set a single callback method for all * reply message types, then use * {@link #replyCallback(ReplyCallback)}. Note that method * overrides all previous set reply callbacks. *

* @param mc the reply message class. * @param cb callback for the reply message. * @throws NullPointerException * if {@code mc} is {@code null}. * @throws IllegalArgumentException * if {@code mc} is not a reply for this request. * @throws IllegalStateException * if this feed is either closed or subscribed. */ @Override public void replyCallback(final Class mc, final ReplyCallback cb) { Objects.requireNonNull(mc, "mc is null"); if (!mReplyCallbacks.containsKey(mc)) { throw ( new IllegalArgumentException( mc.getSimpleName() + " is not a " + mMsgClass.getSimpleName() + " reply")); } if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { throw ( new IllegalStateException( "subscription in place")); } mReplyCallbacks.put(mc, cb); } // end of replyCallback(Class, ReplyCallback) // // end of IERequestFeed Interface Implementations. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Implementations. // /** * Returns a newly minted subordinate request feed for the * given key. The returned feed's configuration is the same * as existing subordinate feeds. * @param key create a request feed for this key. * @return a subordinate request feed. */ @Override protected ERequestFeed createFeed(final EMessageKey key) { final ERequestor requestor = (ERequestor) mEClient.target(); return (ERequestFeed.open(requestor, key, mScope, EClient.ClientLocation.LOCAL, true)); } // end of createFeed(EMessageKey) /** * Sets the status and reply callbacks as per the multi-key * configuration and subscribes the subordinate feeds. * @param feed advertise this subordinate request feed. */ @Override protected void putFeedInPlace(final ERequestFeed feed) { // Must set the callbacks (if any) before subscribing. feed.statusCallback(mStatusCallback); feed.replyCallbacks(mReplyCallbacks); feed.subscribe(); } // end of putFeedInPlace(ERequestFeed) // // end of Abstract Method Implementations. //----------------------------------------------------------- /** * Returns an open multi-key feed for the given request * message class and message subjects list. Once opened, * the caller can (optionally) set the feed status and * reply callbacks and subject the feed just like * {@link ERequestFeed}. *

* It is the subordinate request feeds which call back to * {@code client}, not the opened multi-key feed. So if * {@code subjects} contains 1,000 subjects, then * {@code client} receives status callbacks from 1,000 * subordinate request feeds. *

*

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

* @param client the eBus requestor opening this feed. * @param mc request message class. All feeds apply to * this message class. * @param subjects list of request 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 requestor feed for the given * application object, request 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(ERequestor, Class, Pattern, EFeed.FeedScope) * @see #subscribe() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ public static EMultiRequestFeed open(final ERequestor client, final Class mc, final List subjects, final FeedScope scope) { return (openList(client, mc, subjects, scope, null, ClientLocation.LOCAL, sSubFactory, sMultiFactory)); } // end of open(ERequestor, Class, List<>, FeedScope) /** * Returns an open multi-key request feed for a given * request message class and multiple message subjects. * Once opened, the caller can (optionally) set the status * and reply callbacks and subscribe the feed just like * {@link ERequestFeed}. *

* The subordinate request feeds are selected based on the * given request message class and the regular expression * query. The multi-key request feed is opened whether the * message key dictionary entries match the message class and * subject query or not. In either case, the application may * {@link #addFeed(String) add} more subordinate feeds to the * returned multi-key feed. *

*

* Note: {@code client} receives callbacks * from subordinate {@code ERequestFeed} feeds, not the * multi-key feed. If {@code mc} and {@code query} match * 1,000 request message keys, then {@code client} will * receive feed status and notify callbacks from those 1,000 * subordinate feeds. *

* @param client application object subscribing to the * request message class and matching 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. * @return a new multi-key request feed for the given * application object and request message keys matching the * request message class and query. * * @see #open(ERequestor, Class, List, EFeed.FeedScope) * @see #subscribe() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ public static EMultiRequestFeed open(final ERequestor client, final Class mc, final Pattern query, final FeedScope scope) { return (openQuery(client, mc, query, scope, null, ClientLocation.LOCAL, sSubFactory, sMultiFactory)); } // end of open(ERequestor, Class, Pattern, FeedScope) /** * Subscribes each subordinate {@link ERequestFeed}. If this * feed is currently subscribed, then does nothing. The * requestor client will receive a * {@link ERequestor#feedStatus(EFeedState, IERequestFeed)} * callback from each subordinate request feed. * @throws IllegalStateException * if this feed is closed or the client did not override * not put in place the required callback methods. * * @see #unsubscribe() * @see #close() */ @Override public void subscribe() { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } // If not already subscribed, then subscribe now. if (!mInPlace) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format( "%s multi-key requestor %d: subscribing (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Subscribe each subordinate feed. mFeeds.values() .stream() .map( feed -> { feed.statusCallback(mStatusCallback); feed.replyCallbacks(mReplyCallbacks); return feed; }) .forEachOrdered(ERequestFeed::subscribe); mInPlace = true; } } // end of subscribe() /** * Retracts this multi-key request feed by un-subscribing * each subordinate request feed. Does nothing if this feed * is not currently subscribed. * @throws IllegalStateException * if this multi-key request feed is closed. * * @see #subscribe() * @see #close() */ @Override public void unsubscribe() { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format( "%s multi-key requestor %d: unsubscribing (%s).", mEClient.location(), mEClient.clientId(), mScope)); } // Unadvertise each subordinate feed. mFeeds.values() .stream() .forEachOrdered(ERequestFeed::unsubscribe); // This feed is no longer subscribed. mInPlace = false; } } // end of unsubscribe() /** * Posts a request message to all replier via the subordinate * request feed matching the message's key. * @param msg post this request message to the matching * subordinate feed. * @return the {@link ERequestFeed.ERequest} feed used to * interact with the active request. * @throws NullPointerException * if {@code msg} is {@code null}. * @throws IllegalArgumentException * if {@code msg} message key does not reference a known * subordinate reply feed. * @throws IllegalStateException * if this feed is inactive, not advertised, or there are no * repliers available to respond to the request. */ public ERequest request(final ERequestMessage 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 subscription in place? if (!mInPlace) { // No. Gotta do that first. throw ( new IllegalStateException( "feed not subscribed")); } // 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")); } // Pass the message to the subordinate request and let // it do the additional checks. return ((mFeeds.get(subject)).doRequest(msg)); } // end of request(ERequestMessage) /** * Returns an open multi-key feed for the given request * message class and message subjects list. Once opened, * the caller can (optionally) set the feed status and * reply callbacks and subject the feed just like * {@link ERequestFeed}. *

* It is the subordinate request feeds which call back to * {@code client}, not the opened multi-key feed. So if * {@code subjects} contains 1,000 subjects, then * {@code client} receives status callbacks from 1,000 * subordinate request feeds. *

*

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

* @param client the eBus requestor opening this feed. * @param mc request message class. All feeds apply to * this message class. * @param subjects list of request 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. * @param l {@code client} location. * @return a new multiple key requestor feed for the given * application object, request 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(ERequestor, Class, Pattern, EFeed.FeedScope) * @see #subscribe() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ /* package */ static EMultiRequestFeed open(final ERequestor client, final Class mc, final List subjects, final FeedScope scope, final ClientLocation l) { return (openList(client, mc, subjects, scope, null, l, sSubFactory, sMultiFactory)); } // end of open(...) /** * Returns an open multi-key request feed for a given * request message class and multiple message subjects. * Once opened, the caller can (optionally) set the status * and reply callbacks and subscribe the feed just like * {@link ERequestFeed}. *

* The subordinate request feeds are selected based on the * given request message class and the regular expression * query. The multi-key request feed is opened whether the * message key dictionary entries match the message class and * subject query or not. In either case, the application may * {@link #addFeed(String) add} more subordinate feeds to the * returned multi-key feed. *

*

* Note: {@code client} receives callbacks * from subordinate {@code ERequestFeed} feeds, not the * multi-key feed. If {@code mc} and {@code query} match * 1,000 request message keys, then {@code client} will * receive feed status and notify callbacks from those 1,000 * subordinate feeds. *

* @param client application object subscribing to the * request message class and matching 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 l {@code client} location. * @return a new multi-key request feed for the given * application object and request message keys matching the * request message class and query. * * @see #open(ERequestor, Class, List, EFeed.FeedScope) * @see #subscribe() * @see #close() * @see #addFeed(String) * @see #closeFeed(String) */ /* package */ static EMultiRequestFeed open(final ERequestor client, final Class mc, final Pattern query, final FeedScope scope, final ClientLocation l) { return (openQuery(client, mc, query, scope, null, l, sSubFactory, sMultiFactory)); } // end of open(...) } // end of class EMultiRequestFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy