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

net.sf.eBus.client.EMultiFeed 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 com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.client.EFeed.FeedScope;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.util.TernarySearchTree;
import net.sf.eBus.util.regex.Pattern;

/**
 * Multiple key feeds act as a proxy between application objects
 * and multiple subordinate simple feeds ({@link EPublishFeed},
 * {@link ESubscribeFeed}, {@link EReplyFeed}, and
 * {@link ERequestFeed}), all for a single message class. The
 * multi-key feed keeps the subordinate feeds in the same state:
 * open, advertised/subscribed, or closed. The subordinate feeds
 * are configured with the same eBus client and callback methods.
 * 

* This base class tracks all currently open subordinate feeds * and the overall feed state. *

* * @param all feeds are for this message type. * @param the subordinate feed type. * * @author Charles W. Rapp */ public abstract class EMultiFeed extends EFeed { //--------------------------------------------------------------- // Inner classes. // /** * This interface is used by {@code EMultiFeed} to * instantiate a new subordinate feed for a multi-key feed. * @param client role for the subordinate feed. * @param subordinate feed type. */ @FunctionalInterface protected static interface SubordinateFeedFactory { /** * Returns a newly instantiated {@link EFeed}-subclass * instance based on the given parameters. * @param client application object opening the multi-key * feed. * @param key open subordinate feed for this message * key. * @param scope feed scope is local, remote, or both. * @param condition optional feed condition. Ignored if * subordinate feed does not support conditions. * @param location client location. Will always be * {@link ClientLocation#LOCAL local} because only local * clients may create multi-key feeds. * @return new subordinate feed. */ F newFeed(R client, EMessageKey key, FeedScope scope, ECondition condition, ClientLocation location); } // end of SubordinateFeedFactory /** * This interface is used by {@code EMultiFeed} to * instantiate a new multi-key, sub-class feed. * @param multi-key feed class. * @param feed message class. * @param subordinate feed class. */ @FunctionalInterface protected static interface MultiFeedFactory { /** * Returns a newly instantiated multi-key feed based on * the given parameters. * @param client eBus client encapsulating the * application object. * @param mc feed message class. * @param scope feed scope is local, remote, or both. * @param condition optional feed condition. Ignored if * subordinate feed does not support conditions. * @param feeds initial subordinate feeds. * @return new multi-key feed. */ M newFeed(EClient client, Class mc, FeedScope scope, ECondition condition, Map feeds); } // end of MultiFeedFactory //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link #key()} returns a message key with the subject * {@value}. */ public static final String MULTIFEED_SUBJECT = "__MULTIFEED__"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. Shared by all subclasses. */ protected static final Logger sLogger = Logger.getLogger(EFeed.class.getName()); //----------------------------------------------------------- // Locals. // /** * All subordinate feeds apply to this message class. This * message class is used when {@link #addFeed(String) adding} * new feeds to this multi-key feed. */ protected final Class mMsgClass; /** * 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; /** * Contains the subordinate feeds currently opened by this * multi-key feed. Maps the {@link EMessageKey#subject} to * the subordinate feed. */ protected final Map mFeeds; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new multiple key feed for the given eBus client, * message class, scope, and initial feeds. Subordinate feeds * use the same {@code client}, {@code msgClass}, * {@code scope}, and (optionally) {@code condition}. * @param client connect this client to the subordinate feeds. * @param msgClass all feeds apply to this message class. * @param scope feed scope. * @param feeds initial subordinate feeds. * @param condition condition applied to all subordinate * feeds. Will be {@code null} for feeds which do not use a * condition. */ protected EMultiFeed(final EClient client, final Class msgClass, final FeedScope scope, final ECondition condition, final Map feeds) { super (client, scope); mMsgClass = msgClass; mFeeds = feeds; mCondition = condition; } // end of EMultiFeed() // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Returns a newly minted subordinate feed for the given * message key. This method is used by * {@link #addFeed(String)} to create a subordinate feed. * @param key create feed for this key. * @return a subordinate feed. */ protected abstract F createFeed(final EMessageKey key); /** * Advertises/subscribes the given feed. This method is * used by {@link #addFeed(String)} to adverise/subscribe * the added feed. * @param feed feed to advertised/subscribed. */ protected abstract void putFeedInPlace(final F feed); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Overrides. // /** * Closes all currently open subordinate feeds and clears * the feeds map. */ @Override protected final void inactivate() { mFeeds.values().forEach(EFeed::close); mFeeds.clear(); } // end of inactivate() // // end of Abstract Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns the multi-key feed message class. All * {@link #addFeed(String) added} feeds are created with this * class. * @return feed message class. */ public final Class messageClass() { return (mMsgClass); } // end of messageClass() /** * Returns {@code true} if all subordinate feeds are up and * {@code false} otherwise. * @return {@code true} if all subordinate feeds are up. */ @Override public final boolean isFeedUp() { final Iterator fit = (mFeeds.values()).iterator(); boolean retcode = true; while (fit.hasNext() && retcode) { retcode = (fit.next()).isFeedUp(); } return (retcode); } // end of isFeedUp() /** * Returns a message key with {@link #messageClass()} as the * class and {@link #MULTIFEED_SUBJECT} as the subject. * @return multi-feed message key. */ @Override public final EMessageKey key() { return (new EMessageKey(mMsgClass, MULTIFEED_SUBJECT)); } // end of key() /** * Returns the specified feed's state. A down state means * that messages may not be sent on or received from this * feed. An up state means that messages may possibly be * sent on or received from this feed. *

* If {@code subject} references an unknown feed or the feed * is not in place, then {@link EFeedState#UNKNOWN} is * returned. *

* @param subject return the publish state for this message * subject. * @return the specified feed's state or {@code UNKNOWN} if * {@code subject} does not reference a known feed. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-key feed is closed. */ public final EFeedState feedState(final String subject) { Objects.requireNonNull(subject, "subject is null"); if (subject.isEmpty()) { throw ( new IllegalArgumentException( "subject is an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } final F feed = mFeeds.get(subject); return (feed == null ? EFeedState.UNKNOWN : feed.feedState()); } // end of feedState(String) /** * Returns {@code true} if the given subject is part of this * multi-feed. * @param subject check if this subject is in the feed. * @return {@code true} if subject is part of multi-feed. */ public final boolean isSubject(final String subject) { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } return (mFeeds.containsKey(subject)); } // end of isSubject(String) /** * Returns the current subordinate feed message subjects. * The returned list is a copy of the actual message subjects * list. Modifying the returned list has no impact on eBus * operations. * @return subordinate feed message subjects. * @throws IllegalStateException * if this multi-key feed is closed. */ public final List subjects() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } final List retval = new ArrayList<>(mFeeds.size()); mFeeds.values() .forEach( feed -> retval.add((feed.key()).subject())); return (retval); } // end of subjects() /** * Returns the current subordinate feed message keys. The * returned list is a copy of the actual message key list. * Modifying the returned list has no impact on eBus * operations. * @return subordinate feed message keys. * @throws IllegalStateException * if this multi-key feed is closed. */ public final List keys() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } final List retval = new ArrayList<>(mFeeds.size()); mFeeds.values().forEach(feed -> retval.add(feed.key())); return (retval); } // end of keys() /** * Returns a mapping between the current subordinate feed * message keys and the subordinate feed state. The returned * map may be freely modified without impact to this * multi-feed data members. * @return state of current subordinate feeds. */ public final Map feedStates() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } final Map retval = new HashMap<>(mFeeds.size()); mFeeds.values() .forEach( feed -> retval.put(feed.key(), feed.feedState())); return (retval); } // end of feedStates() // // end of Get Methods. //----------------------------------------------------------- /** * Adds a new feed based on the configured message class and * the given subject. If this multi-key feed is advertised or * subscribed, then the new subordinate feed is * advertised/subscribed. This method does nothing if there * is a subordinate feed for {@code subject} in place. *

* The newly created feed references the same * {@link #mEClient client}, {@link #mMsgClass message class}, * {@link #mScope scope} and * (optional) {@link ECondition condition} as all the other * subordinate feeds in this multi-key feed. It will also use * the same callbacks as existing subordinate feeds. *

* @param subject add key with this message subject. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-key feed is closed. * * @see #addAllFeeds(List) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) */ public final void addFeed(final String subject) { if (Strings.isNullOrEmpty(subject)) { throw ( new IllegalArgumentException( "subject is either null or an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } // Is this key unique? if (!mFeeds.containsKey(subject)) { // Yes, this is a new feed. final F feed = createFeed( new EMessageKey(mMsgClass, subject)); mFeeds.put(subject, feed); if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "%s multi-key feed %d (%s): adding feed %s.", mEClient.location(), mEClient.clientId(), mScope, subject)); } // Is this multikey feed subscribed? if (mInPlace) { // Yes, put this new feed in place. putFeedInPlace(feed); } } } // end of addFeed(String) /** * Adds new feeds based on the configured message class and * the subjects list. If this multi-key feed is in place * (advertised or subscribed), then the new subordinate feeds * are put in place as well. If {@code subjects} contains * feeds that already exist, the existing feed is neither * replaced or modified. *

* The newly created feeds reference the same * {@link #mEClient client}, {@link #mMsgClass message class}, * {@link #mScope scope} and * (optional) {@link ECondition condition} as all the other * subordinate feeds in this multi-key feed. They will also * use the same callbacks as existing subordinate feeds. *

* @param subjects create new feeds for these subjects. * @throws NullPointerException * if {@code subjects} is {@code null} or contains a * {@code null} element. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. * @throws IllegalStateException * if this multi-key feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) */ public final void addAllFeeds(final List subjects) { Objects.requireNonNull(subjects, "subjects is null"); // Are all the subject non-null and not empty? if (subjects.contains(null)) { throw ( new NullPointerException( "subjects contains a null value")); } if (subjects.contains("")) { throw ( new IllegalArgumentException( "subjects contains an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } addFeeds(subjects); } // end of addAllFeeds(List) /** * Adds new feeds for each of the subjects matching the given * query. If there are no matching subjects, then no feeds * are added. If {@code query} matches an existing feed * subject, then the existing feed is neither modified not * replaced. The new feeds are for this multi-key feed's * message class. * @param query find all subjects matching this query. * @throws NullPointerException * if {@code query} is {@code null}. * @throws IllegalStateException * if this multi-key feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(List) * @see #closeFeed(String) */ public final void addAllFeeds(final Pattern query) { Objects.requireNonNull(query, "query is null"); // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } // Find the message keys matching the given regular // expression query. final Pattern keyPattern = Pattern.compile(mMsgClass.getName() + EMessageKey.KEY_IFS + query.pattern()); final List subjects = new ArrayList<>(); // Extract the subjects from the matching message keys. ESubject.findKeys(keyPattern) .forEach(key -> subjects.add(key.subject())); addFeeds(subjects); } // end of addAllFeeds(Pattern) /** * Removes and closes the feed for the given message subject. * Does nothing if {@code subject} does not reference a * known subordinate feed. * @param subject remove and close the feed for this subject. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-key feed is closed. * * @see #addFeed(String) */ public final void closeFeed(final String subject) { Objects.requireNonNull(subject, "subject is null"); if (subject.isEmpty()) { throw ( new IllegalArgumentException( "subject is an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-key feeds. throw ( new IllegalStateException("feed is inactive")); } // Is this a known subordinate feed? if (mFeeds.containsKey(subject)) { // Yes. Remove and close the subordinate feed. (mFeeds.remove(subject)).close(); if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "%s multi-key feed %d (%s): removed feed %s.", mEClient.location(), mEClient.clientId(), mScope, subject)); } } } // end of closeFeed(String) /** * Returns a new, opened multi-key feed for the given * parameters. Does the actual work of validating the * parameters and creating the initial subordinate feeds. * @param eBus role used with this multi-key feed. * @param feed based on this message class. * @param subordinate feed class. * @param multi-key feed class. * @param client application object interacting with the * feed. * @param mc feed message class. * @param subjects message subject list. May not contain * null or empty strings. * @param scope whether the feed supports local feeds, * remote feeds, or both. * @param condition the optional condition used by the * subordinate feed. * @param loc {@code client} location. * @param subFactory used to instantiate a new subordinate * feed. * @param multiFactory used to instantiate a new multi-key * feed. * @return a new, open multiple key feed for the given * application object, message class, and subjects. * @throws NullPointerException * if any of the arguments is {@code null}. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. */ protected static > M openList(final R client, final Class mc, final List subjects, final FeedScope scope, final ECondition condition, final ClientLocation loc, final SubordinateFeedFactory subFactory, final MultiFeedFactory multiFactory) { // Validate the parameters. // Are required parameters non-null references? Objects.requireNonNull(client, "client is null"); Objects.requireNonNull(mc, "mc is null"); Objects.requireNonNull(subjects, "subjects is null"); Objects.requireNonNull(scope, "scope is null"); // Are all the subject non-null and not empty? if (subjects.contains(null)) { throw ( new NullPointerException( "subjects contains a null value")); } if (subjects.contains("")) { throw ( new IllegalArgumentException( "subjects contains an empty string")); } return (openMultiFeed(client, mc, subjects, scope, condition, loc, subFactory, multiFactory)); } // end of openList(...) /** * Returns a new, opened multi-key feed for the given message * class and subject regular expression query. A subordinate * feed is opened for all message keys matching the message * class and subject query. If there are no matching message * keys, then the feed is opened with no initial subordinate * feeds. * @param eBus role used with this multi-key feed. * @param feed based on this message class. * @param subordinate feed class. * @param multi-key feed class. * @param client application object interacting with the * feed. * @param mc feed message class. * @param query message subject regular expression query. * @param scope whether the feed supports local feeds, * remote feeds, or both. * @param condition the optional condition used by the * subordinate feed. * @param loc {@code client} location. * @param subFactory used to instantiate a new subordinate * feed. * @param multiFactory used to instantiate a new multi-key * feed. * @return a new, open multiple key feed for the given * application object, message class, and subject query. May * have no feeds initially. * @throws NullPointerException * if any of the arguments is {@code null}. */ protected static > M openQuery(final R client, final Class mc, final Pattern query, final FeedScope scope, final ECondition condition, final ClientLocation loc, final SubordinateFeedFactory subFactory, final MultiFeedFactory multiFactory) { // Validate the parameters. // Are required parameters non-null references? Objects.requireNonNull(client, "client is null"); Objects.requireNonNull(mc, "mc is null"); Objects.requireNonNull(query, "query is null"); Objects.requireNonNull(scope, "scope is null"); // Find the message keys matching the given regular // expression query. final Pattern keyPattern = Pattern.compile(mc.getName() + EMessageKey.KEY_IFS + query.pattern()); final List subjects = new ArrayList<>(); // Extract the subjects from the matching message keys. ESubject.findKeys(keyPattern) .forEach(key -> subjects.add(key.subject())); return (openMultiFeed(client, mc, subjects, scope, condition, loc, subFactory, multiFactory)); } // end of openQuery(...) /** * Performs the actual work of opening a new multi-key feed * using the given parameters. Creates the initial * subordinate feeds for each {@code subject}. * @param eBus role used with this multi-key feed. * @param feed based on this message class. * @param subordinate feed class. * @param multi-key feed class. * @param client application object interacting with the * feed. * @param mc feed message class. * @param fc feed class. * @param subjects list of supported message subjects which * are combined with {@code mc} to create feed * {@link EMessageKey message keys}. * @param scope whether the feed supports local feeds, * remote feeds, or both. * @param condition the optional condition used by the * subordinate feed. * @param loc {@code client} location. * @param query optional query for a pattern-based multi-feed * which supports dynamic new feeds. * @param subFactory used to instantiate a new subordinate * feed. * @param multiFactory used to instantiate a new multi-key * feed. * @return a new, open multiple key feed for the given * application object, message class, and subject query. May * have no feeds initially. */ private static > M openMultiFeed(final R client, final Class mc, final List subjects, final FeedScope scope, final ECondition condition, final ClientLocation loc, final SubordinateFeedFactory subFactory, final MultiFeedFactory multiFactory) { final EClient eClient = EClient.findOrCreateClient(client, loc); final Map feeds = new TernarySearchTree<>(); final M retval; // Create the initial subordinate feeds. subjects.forEach( subject -> feeds.put( subject, subFactory.newFeed( client, new EMessageKey(mc, subject), scope, condition, loc))); retval = multiFactory.newFeed(eClient, mc, scope, condition, feeds); eClient.addFeed(retval); return (retval); } // end of openMultiFeed(...) /** * Creates the new feeds for each of the list subjects. * @param subjects new feed subjects. */ private void addFeeds(final List subjects) { subjects.stream() // Filter out duplicate feed subjects. .filter(s -> (!mFeeds.containsKey(s))) .forEachOrdered( s -> { final F feed = createFeed( new EMessageKey(mMsgClass, s)); mFeeds.put(s, feed); if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "%s multi-key feed %d (%s): adding feed %s.", mEClient.location(), mEClient.clientId(), mScope, s)); } // Is this multikey feed subscribed? if (mInPlace) { // Yes, put this new feed in place. putFeedInPlace(feed); } }); } // end of addFeeds(List<>) } // end of class EMultiFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy