
net.sf.eBus.client.EMultiReplyFeed 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.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, boolean)}.
*/
private CancelRequestCallback mCancelCallback;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new instance of EMultiReplyFeed.
*/
private EMultiReplyFeed(final EClient client,
final Class extends ERequestMessage> 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;
} // 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, boolean)}. A
* {@code null cb} means that cancellations are passed to the
* {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)}
* 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;
} // 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;
}
} // 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));
}
} // 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);
} // 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 extends ERequestMessage> mc,
final List subjects,
final FeedScope scope,
final ECondition condition)
{
return (openList(client,
mc,
subjects,
scope,
condition,
ClientLocation.LOCAL,
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 extends ERequestMessage> mc,
final Pattern query,
final FeedScope scope,
final ECondition condition)
{
return (openQuery(client,
mc,
query,
scope,
condition,
ClientLocation.LOCAL,
sSubFactory,
sMultiFactory));
} // end of open(EReplier,Class,Pattern,boolean,FeedScope)
/**
* 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.
* @param l {@code client} location.
* @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()
*/
/* package */ static EMultiReplyFeed open(final EReplier client,
final Class extends ERequestMessage> mc,
final List subjects,
final FeedScope scope,
final ECondition condition,
final ClientLocation l)
{
return (openList(client,
mc,
subjects,
scope,
condition,
l,
sSubFactory,
sMultiFactory));
} // end of open(...)
/**
* 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.
* @param l {@code client} location.
* @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()
*/
/* package */ static EMultiReplyFeed open(final EReplier client,
final Class extends ERequestMessage> mc,
final Pattern query,
final FeedScope scope,
final ECondition condition,
final ClientLocation l)
{
return (openQuery(client,
mc,
query,
scope,
condition,
l,
sSubFactory,
sMultiFactory));
} // end of open(...)
} // end of class EMultiReplyFeed