
net.sf.eBus.client.ERequestSubject 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 2014 - 2016. Charles W. Rapp
// All Rights Reserved.
//
package net.sf.eBus.client;
import java.util.Iterator;
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.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;
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;
/**
* {@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 methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new request subject instance for the given
* message key.
* @param key the request message key.
*/
@SuppressWarnings ({"unchecked", "rawtypes"})
/* package */ 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)
{
EMessageHeader retval = null;
if (mRepliers.supports(ClientLocation.REMOTE) > 0)
{
retval =
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
new AdMessage(mKey,
adStatus,
MessageType.REPLY,
mRemoteFeedState));
}
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 */
synchronized void subscribe(
final ERequestFeed feed)
{
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format("%s: adding %s/%s requestor %d, feed %d.",
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 Iterator rit =
mRepliers.iterator(feed.scope());
EReplyFeed replier;
while (rit.hasNext())
{
replier = rit.next();
feed.addReplier(replier.location(), replier);
}
return;
} // 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 */ synchronized void
unsubscribe(final ERequestFeed feed)
{
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format("%s: removing %s/%s requestor %d, feed %d.",
mKey,
feed.location(),
feed.scope(),
feed.clientId(),
feed.feedId()));
}
mRequestors.remove(feed);
return;
} // 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 */ synchronized void
advertise(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format("%s: adding %s/%s replier %d, feed %d.",
mKey,
location,
scope,
feed.clientId(),
feed.feedId()));
}
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 Iterator qit =
mRequestors.iterator(FeedScope.LOCAL_AND_REMOTE);
while (qit.hasNext())
{
(qit.next()).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) &&
mRepliers.supports(FeedScope.REMOTE_ONLY) == 1)
{
// Yes. Forward the ad to all remote
// applications.
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format("%s: forward ad to remote applications.",
mKey));
}
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
new AdMessage(
mKey,
AdMessage.AdStatus.ADD,
EMessage.MessageType.REQUEST,
mRemoteFeedState)));
}
return;
} // 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 */ synchronized void
unadvertise(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
final int feedCount = mRepliers.remove(feed);
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format("%s: removing %s/%s replier %d, feed %d.",
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)
{
final Iterator qit =
mRequestors.iterator(FeedScope.LOCAL_AND_REMOTE);
while (qit.hasNext())
{
(qit.next()).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.
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format("%s: forward ad retraction to remote applications (feed count=%d)",
mKey,
feedCount));
}
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.AD).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
new AdMessage(
mKey,
AdMessage.AdStatus.REMOVE,
EMessage.MessageType.REQUEST,
EFeedState.DOWN)));
}
return;
} // 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 */ synchronized void
updateFeedState(final EReplyFeed feed)
{
final ClientLocation location = feed.location();
final FeedScope scope = feed.scope();
final EFeedState oldState = mRemoteFeedState;
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format("%s: updating %s replier %d %s to %s (scope: %s).",
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.
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format("%s: forward %s feed state to remote applications.",
mKey,
mRemoteFeedState));
}
ERemoteApp.forwardAll(
new EMessageHeader(
(SystemMessageType.FEED_STATUS).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
new FeedStatusMessage(mRemoteFeedState)));
}
return;
} // 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.
* @param key request message key.
* @return the request subject for the given message key.
*/
@SuppressWarnings ("unchecked")
/* package */ static ERequestSubject
findOrCreate(final EMessageKey key)
{
ERequestSubject retval;
synchronized (sSubjects)
{
final String keyString = key.keyString();
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);
if (sLogger.isLoggable(Level.FINE))
{
sLogger.finest(
String.format(
"%s: created request subject.",
key));
}
}
}
return (retval);
} // end of findOrCreate(EMessageKey)
//---------------------------------------------------------------
// Member data.
//
/**
* 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;
//-----------------------------------------------------------
// Statics.
//
/**
* The subject class logger.
*/
private static final Logger sLogger =
Logger.getLogger(ERequestSubject.class.getName());
} // end of class ERequestSubject
© 2015 - 2025 Weber Informatics LLC | Privacy Policy