net.sf.eBus.client.ERequestSubject Maven / Gradle / Ivy
The newest version!
//
// Copyright 2014 - 2016, 2020 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 net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.sysmessages.AdMessage;
import net.sf.eBus.client.sysmessages.AdMessage.AdStatus;
import net.sf.eBus.client.sysmessages.FeedStatusMessage;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.messages.EMessage.MessageType;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ERequestMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@code ERequestSubject} connects repliers with requesters
* based on their respective feed scopes. A request is
* satisfied if there is at least one replier within its scope
* whose {@link ECondition condition} accepts the
* {@link ERequestMessage request message}. An
* {@link ERequestFeed.ERequest} is created for each matching
* replier and forwarded to
* {@link EReplier#request(net.sf.eBus.client.EReplyFeed.ERequest)}.
* The replier
* {@link EReplyFeed.ERequest#reply(net.sf.eBus.messages.EReplyMessage) replies}
* via the {@code ERequest} instance rather than via
* {@link EReplyFeed}.
*
* @author Charles Rapp
*/
/* package */ final class ERequestSubject
extends ESubject
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* The subject class logger.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(ERequestSubject.class);
//-----------------------------------------------------------
// Locals.
//
/**
* Currently registered request feeds.
*/
private final EFeedList mRequestors;
/**
* The currently advertised repliers, one list per feed location.
*/
private final EFeedList mRepliers;
/**
* Tracks the reply feed state for remote clients. This is
* necessary since we must send a feed status message to
* remote clients when the feed state transitions between
* UP and DOWN.
*/
private EFeedState mRemoteFeedState;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new request subject instance for the given
* message key.
* @param key the request message key.
*/
@SuppressWarnings ({"unchecked", "rawtypes"})
private ERequestSubject(final EMessageKey key)
{
super (key);
mRequestors = new EFeedList<>();
mRepliers = new EFeedList<>();
mRemoteFeedState = EFeedState.DOWN;
} // end of ERequestSubject(EMessageKey)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// ESubject Abstract Method Implementations.
//
@Override
/* package */ EMessageHeader localAd(final AdStatus adStatus)
{
final EFeedState fs =
mRepliers.feedState(ClientLocation.REMOTE);
EMessageHeader retval = null;
if (fs != EFeedState.UNKNOWN)
{
retval =
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(AdMessage.builder()).messageKey(mKey)
.adStatus(adStatus)
.adMessageType(MessageType.REPLY)
.feedState(mRemoteFeedState)
.build());
}
return (retval);
} // end of localAd(AdStatus)
//
// end of ESubject Abstract Method Implementations.
//-----------------------------------------------------------
/**
* Adds a local request feed to the request feed list. This
* feed is local by definition since request feeds are not
* forwarded to remote eBus applications.
* @param feed add this request feed.
*/
/* package */ void subscribe(final ERequestFeed feed)
{
sLogger.debug("{}: adding {}/{} requestor {}, feed {}.",
mKey,
feed.location(),
feed.scope(),
feed.clientId(),
feed.feedId());
mRequestors.add(feed);
// Iterate over the extant repliers, adding
// repliers *if* they match the requestor's feed and
// scope.
final List feeds =
mRepliers.feeds(feed.scope());
feeds.forEach(r -> feed.addReplier(r.location(), r));
} // end of subscribe(ERequestFeed)
/**
* Removes a local request feed from the request feed list.
* This feed is local by definition since request feeds are
* not forwarded to remote eBus applications.
* @param feed remote this request feed.
*/
/* package */ void unsubscribe(final ERequestFeed feed)
{
sLogger.debug("{}: removing {}/{} requestor {}, feed {}.",
mKey,
feed.location(),
feed.scope(),
feed.clientId(),
feed.feedId());
mRequestors.remove(feed);
} // end of unsubscribe(ERequestFeed)
/**
* Adds the given reply feed to the replier feed list. If
* this is the first local client/local & remote feed,
* then the advertisement is forwarded to all remote eBus
* applications.
* @param feed a replier feed.
*/
/* package */ void advertise(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
final EFeedState remoteFeedState;
sLogger.debug("{}: adding {}/{} replier {}, feed {}.",
mKey,
location,
scope,
feed.clientId(),
feed.feedId());
remoteFeedState = mRepliers.add(feed);
// Forward this advertisement to all request feeds.
// Note: all request feeds are local since they are not
// advertised to remote eBus applications and, therefore,
// match all reply feeds *except* local repliers with a
// remote only scope.
if (location == ClientLocation.REMOTE ||
scope != FeedScope.REMOTE_ONLY)
{
final List feeds =
mRequestors.feeds(FeedScope.LOCAL_AND_REMOTE);
feeds.forEach(f -> f.addReplier(location, feed));
}
// If this feed is local client/local&remote feed or
// remote only and the first one to boot, then forward
// the advertisement to all remote eBus applications
// currently connected.
if (location == ClientLocation.LOCAL &&
(scope == FeedScope.LOCAL_AND_REMOTE ||
scope == FeedScope.REMOTE_ONLY) &&
remoteFeedState != EFeedState.UNKNOWN)
{
// Yes. Forward the ad to all remote
// applications.
sLogger.debug(
"{}: forward ad to remote applications.", mKey);
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(AdMessage.builder()).messageKey(mKey)
.adStatus(AdStatus.ADD)
.adMessageType(MessageType.REQUEST)
.feedState(mRemoteFeedState)
.build()));
}
} // end of subscribe(EReplyFeed)
/**
* Removes the given replier feed from the replier feed list.
* If this is the last local client/local & remote
* feed, then the advertisement is retracted from all remote
* eBus applications.
* @param feed a replier feed.
*/
/* package */ void unadvertise(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
final int feedCount = mRepliers.remove(feed);
sLogger.debug("{}: removing {}/{} replier {}, feed {}.",
mKey,
location,
scope,
feed.clientId(),
feed.feedId());
// Forward this unadvertisement to all request feeds.
// Note: all request feeds are local since they are not
// advertised to remote eBus applications and, therefore,
// match all reply feeds *except* local repliers with a
// remote only scope.
if ((location == ClientLocation.LOCAL &&
scope != FeedScope.REMOTE_ONLY) ||
location == ClientLocation.REMOTE)
{
final List feeds =
mRequestors.feeds(FeedScope.LOCAL_AND_REMOTE);
feeds.forEach(r -> r.removeReplier(feed));
}
// If this feed is local client/local&remote feed and
// the last one to boot, then forward the unadvertisement
// to all remote eBus applications currently connected.
// Is this the last local feed to support remote
// requests?
if (location == ClientLocation.LOCAL &&
(scope == FeedScope.LOCAL_AND_REMOTE ||
scope == FeedScope.REMOTE_ONLY) &&
feedCount == 0)
{
// Yes. Retract the ad to all remote
// applications.
sLogger.debug(
"{}: forward ad retraction to remote applications (feed count={})",
mKey,
feedCount);
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(AdMessage.builder()).messageKey(mKey)
.adStatus(AdStatus.REMOVE)
.adMessageType(MessageType.REQUEST)
.feedState(EFeedState.DOWN)
.build()));
}
} // end of unsubscribe(EReplyFeed)
/**
* Updates the replier feed state as contained in
* {@code feed}. If this feed state change results in
* requestor feed state change, then all interested
* requestors are informed of this change.
* @param feed update this feed's state.
*/
/* package */ void updateFeedState(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
final EFeedState oldState = mRemoteFeedState;
sLogger.debug(
"{}: updating {} replier {} {} to {} (scope: {}).",
mKey,
feed.location(),
feed.clientId(),
feed.key(),
feed.feedState(),
scope);
mRequestors.updateCount(feed, feed.feedState());
// If this update is from a local client and the subject
// feed *from a remote perspective* transitions between
// up and down due to this update, then forward this
// feed state to all remote eBus applications currently
// connected.
if (location == ClientLocation.LOCAL &&
(scope == FeedScope.LOCAL_AND_REMOTE ||
scope == FeedScope.REMOTE_ONLY) &&
(mRemoteFeedState =
mRepliers.feedState(
ClientLocation.REMOTE)) != oldState)
{
// Yes. Forward feed state to remote applications.
sLogger.debug(
"{}: forward {} feed state to remote applications.",
mKey,
mRemoteFeedState);
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.FEED_STATUS).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(FeedStatusMessage.builder()).feedState(mRemoteFeedState)
.build()));
}
} // end of updateFeedState(EReplyFeed)
/**
* Returns the request subject for the given message key. If
* the subject does not already exist, then creates the
* request subject for {@code key} and stores it in the
* subject map.
*
* The caller is expected to verify that {@code key} is a
* non-{@code null} reference to a request message. This
* method does not validate {@code key}.
*
* @param key request message key.
* @return the request subject for the given message key.
*/
@SuppressWarnings ("unchecked")
/* package */ static ERequestSubject findOrCreate(final EMessageKey key)
{
final ERequestSubject retval;
synchronized (sSubjects)
{
retval = doFindOrCreate(key);
}
return (retval);
} // end of findOrCreate(EMessageKey)
/* package */ static ERequestSubject doFindOrCreate(final EMessageKey key)
{
final String keyString = key.keyString();
ERequestSubject retval;
retval = (ERequestSubject) sSubjects.get(keyString);
// Does this subject already exist?
if (retval == null)
{
// No. So create it now.
retval = new ERequestSubject(key);
sSubjects.put(keyString, retval);
sLogger.trace("{}: created request subject.", key);
// Let listeners know about this new request subject.
ESubject.forwardUpdate(SubjectType.REQUEST, key);
}
return (retval);
} // end of doFindOrCreate(EMessageKey)
} // end of class ERequestSubject