
net.sf.eBus.client.EMulticastPublisher Maven / Gradle / Ivy
//
// Copyright 2021 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.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.sysmessages.McastKeyMessage;
import net.sf.eBus.client.sysmessages.McastSubscribeMessage;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.config.EConfigure.MulticastConnection;
import net.sf.eBus.config.EConfigure.MultifeedType;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for publishing notification messages
* to its multicast connection. This means it maintains
* one or more {@link EMultiSubscribeFeed multi-feed subscription}
* to eBus. The notification messages received from this
* multi-feed are then posted to the multicast connection. A
* multi-feed subscription key may be either a message class and
* a fixed list of subjects or a message class and a pattern.
* If a subject list, then only those subjects will be supported
* by the multicast publisher.
*
* If any of the configured multicast keys is a dynamic pattern
* then this publisher
* {@link ESubject#addListener(ISubjectListener) subscribes to subject updates}.
* New notification subjects matching the multicast publisher
* message key patterns are automatically added to the multi-feed
* subscriber and published to the multicast channel.
*
*
* @see EMulticastConnection
* @see EMulticastSubscriber
*
* @author Charles W. Rapp
*/
public final class EMulticastPublisher
extends EMulticastConnection
implements ESubscriber,
ISubjectListener
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(EMulticastPublisher.class);
//-----------------------------------------------------------
// Locals.
//
/**
* Stores notification multi-feed subscriptions.
*/
private final Map, EMultiSubscribeFeed> mFeeds;
/**
* Assign the next new message key this identifier. Starts at
* zero.
*/
private int mNextId;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new instance of EMulticastPublisher.
*/
/* package */ EMulticastPublisher(final MulticastConnection config)
{
super (config);
mFeeds = new HashMap<>();
mNextId = 0;
} // end of EMulticastPublisher(MulticastConnection)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Overrides.
//
/**
* No special steps are taken when opening a multicast
* publisher.
*/
// This callback does nothing and so the method body is
// empty.
@SuppressWarnings({"java:S1186"})
@Override
protected void onOpen()
{}
/**
* Posts the current feed status for concrete
* application message key.
*/
@Override
protected void onConnect()
{
// Post current KeyInfo settings.
sLogger.debug(
"{}: sending current feed state for application message keys.",
mName);
mKeyIds.values()
.stream()
.filter(k -> k.keyId() >= 0)
.forEach(this::sendFeedState);
} // end of onConnect()
/**
* Sends a down feed state for each of the message keys to
* the multicast group.
*/
@Override
protected void onDisconnect()
{
sLogger.debug(
"{}: sending feed down for application message keys.",
mName);
// Since KeyInfo tracks the actual message key state
// as per the eBus feed, we do not want to lose that
// information so that on re-connecting that state can
// be posted again. So use a temporary KeyInfo instance
// to report that the feed is down as far as the
// multicast group is concerned.
mKeyIds.values()
.stream()
.filter(k -> k.keyId() >= 0)
.forEach(
k ->
sendFeedState(
new KeyInfo(k, EFeedState.DOWN)));
} // end of onDisconnect()
/**
* No special steps are taken when closing a multicast
* publisher feed.
*/
// This required callback does nothing and so the method body
// is empty.
@SuppressWarnings({"java:S1186"})
@Override
protected void onClose()
{}
/**
* Handles messages received on the multicast group. The
* only message that is used is {@link McastSubscribeMessage}
* which multicast subscribers send to request that joined
* publishers send their current feed states. All other
* messages are ignored.
* @param msg inbound multicast message.
* @param source message came from this source address.
*
* @see #feedStateRequest()
*/
@Override
protected void onMessage(final EMessage msg,
final InetSocketAddress source)
{
// Is it a new subscriber request for our current feeds?
if (McastSubscribeMessage.class.equals(msg.getClass()))
{
if (sLogger.isTraceEnabled())
{
sLogger.trace("{}: received {} message:\n{}",
mName,
msg.key(),
msg);
}
else
{
sLogger.debug("{}: received {} message.",
mName,
msg.key());
}
// Because this method is called on the selector
// thread, have this message processed on the
// dispatcher thread.
mEClient.dispatch(this::feedStateRequest);
}
// Else this must be another publisher's message
// (notification or multicast feed update).
// Not interested in that.
} // end of onMessage(EMessage, InetSocketAddress)
//
// end of Abstract Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Creates and subscribes to the {@code EMultiSubscribeFeed}s
* defined in the connection configuration. If any of these
* feeds are dynamic-query based, then also listens for new
* notification subjects.
*/
@Override
public void startup()
{
createFeeds();
subscribeFeeds();
if (mIsDynamic)
{
sLogger.debug("{}: listening for subject updates.",
mName);
ESubject.addListener(this);
}
// Now let EMulticastConnection do its thing.
super.startup();
} // end of startup()
/**
* Closes all multi-feed subscriptions and then discards
* them.
*/
@Override
public void shutdown()
{
final Collection feeds =
new ArrayList<>(mFeeds.values());
sLogger.debug(
"{}: closing EMultiSubscribeFeeds.", mName);
mFeeds.clear();
feeds.forEach(EMultiSubscribeFeed::close);
// Now let EMulticastConnection do its thing.
super.shutdown();
} // end of shutdown()
//
// end of EObject Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// ISubjectListener Interface Implementation.
//
/**
* If subject is a notification then check if the subject's
* message key matches any of the multicast keys. If so,
* then add the subject to the matching multi-feed.
* @param subjectType either notification or request.
* @param subjectKey subject's message key.
*/
@Override
public void subjectUpdate(final SubjectType subjectType,
final EMessageKey subjectKey)
{
// Only interested in notification message subjects.
// Note: we know this multicast publisher has at least
// one dynamic multi-feed query since this listener is
// put in place only in that case.
if (subjectType == SubjectType.NOTIFICATION)
{
final MulticastKey mkey =
mMulticastKeys.get(subjectKey.className());
sLogger.trace(
"{}: checking if {} matches dynamic query feeds.",
mName,
subjectKey);
// Was a matching key found?
if (mkey != null &&
mkey.matches(subjectKey.subject()))
{
// Yes. Add this subject to the feed.
// Note: don't need to check for null: if the
// message class is in mMulticastKeys, it is in
// mFeeds.
final EMultiSubscribeFeed feed =
mFeeds.get(mkey.messageClass());
final String subject = subjectKey.subject();
sLogger.debug(
"{}: adding {} to multi-feed subscription.",
mName,
subjectKey);
feed.addFeed(subject);
// Assign and multicast message key unique
// identifier.
assignKeyId(subjectKey, feed.feedState(subject));
}
}
} // end of subjectUpdate(SubjectType, EMessageKey)
//
// end of ISubjectListener Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// ESubscriber Interface Implementation.
//
/**
* If {@code feed} is a supported message, then stores this
* latest feed state. If this multicast publisher is joined
* to its group then publishes this feed state update to the
* group.
* @param feedState latest feed state.
* @param feed state update applies to this feed.
*/
@Override
public void feedStatus(final EFeedState feedState,
final IESubscribeFeed feed)
{
final EMessageKey key = feed.key();
final KeyInfo keyInfo = mKeyIds.get(key);
// Is this a supported message key?
if (keyInfo != null)
{
// Yes. Store away this latest feed state.
sLogger.debug("{}: setting feed {} state to {}.",
mName,
feed.key(),
feedState);
keyInfo.state(feedState);
// Currently joined to multicast group?
if (isJoined())
{
// Yes. Multicast state update.
sendFeedState(keyInfo);
}
}
} // end of feedStatus(EFeedState, IESubscribeFeed)
/**
* Forwards eBus notification messages to multicast group,
* if joined.
* @param msg received notification message.
* @param feed notification message subscription.
*/
@Override
public void notify(final ENotificationMessage msg,
final IESubscribeFeed feed)
{
final EMessageKey key = feed.key();
final KeyInfo keyInfo = mKeyIds.get(key);
// Multicast this message if joined to the multicast
// group and the key is known.
if (isJoined() && keyInfo != null)
{
final EMessageHeader header =
new EMessageHeader(
keyInfo.keyId(), feed.feedId(), 0, msg);
if (sLogger.isTraceEnabled())
{
sLogger.trace(
"{}: forwarding {} message to group {}:\n{}",
mName,
key,
mTarget.getHostString(),
msg);
}
else
{
sLogger.debug(
"{}: forwarding {} message to group {}.",
mName,
key,
mTarget.getHostString());
}
send(header, mTarget);
}
} // end of notify(ENotificationMessage, IESubscribeFeed)
//
// end of ESubscriber Interface Implementation.
//-----------------------------------------------------------
/**
* Creates the {@code EMultiSubscribeFeed}s checking if any
* of these feeds is a dynamic-query based.
*/
private void createFeeds()
{
Class extends ENotificationMessage> mc;
EMultiSubscribeFeed.Builder builder;
EMultiSubscribeFeed feed;
sLogger.debug("{}: open EMultiSubscribeFeeds.", mName);
for (MulticastKey key : mMulticastKeys.values())
{
mc = key.messageClass();
mIsDynamic = (mIsDynamic || key.isDynamic());
builder = EMultiSubscribeFeed.builder();
sLogger.trace("{}: opening multi-feed {}.",
mName,
key);
// Fixed subject list or dynamic subject query?
if (key.feedType() == MultifeedType.LIST)
{
// Fixed subject list.
feed = builder.target(this)
.messageClass(mc)
.subjects(key.subjectList())
// Local because this client is
// taking inbound multicast
// notifications from local
// publishers and forwarding them
// to the multicast group.
.scope(FeedScope.LOCAL_ONLY)
.location(ClientLocation.REMOTE)
.build();
}
// Dynamic subject query.
else
{
feed = builder.target(this)
.messageClass(mc)
.query(key.subjectQuery())
// Local because this client is
// taking inbound multicast
// notifications from local
// publishers and forwarding them
// to the multicast group.
.scope(FeedScope.LOCAL_ONLY)
.location(ClientLocation.REMOTE)
.build();
}
mFeeds.put(mc, feed);
}
} // end of createFeeds()
/**
* Puts the {@code EMultiSubcribeFeed}s in place.
*/
private void subscribeFeeds()
{
sLogger.debug("{}: subscribing to EMultiSubscribeFeeds.",
mName);
mFeeds.values().forEach(key -> subscribeFeed(key));
} // end of subscribeFeeds()
/**
* Subscribes to given notification multi-feed, assigning a
* unique integer identifier to each message key. This
* identifier is used in the message encoding.
* @param feed subscribe to this multi-feed.
*/
private void subscribeFeed(final EMultiSubscribeFeed feed)
{
// For each message key in the feed:
// 1. Assign a unique integer identifier to the feed.
// 2. Send the multicast subject message containing
// the message key, identifier, and current feed
// state (UNKNOWN).
// After that, put the multi-feed subscription in place.
feed.keys()
.forEach(
key ->
assignKeyId(
key, feed.feedState(key.subject())));
feed.subscribe();
} // end of subscribeFeed(EMultiSubscribeFeed)
/**
* Assigns a unique integer identifier to the given message
* key and multicasts this identifier-to-message key mapping.
* @param key assign identifier for this key.
*/
private void assignKeyId(final EMessageKey key,
final EFeedState state)
{
final KeyInfo keyInfo =
new KeyInfo(key, null, MCAST_ID, mNextId, state);
++mNextId;
mKeyIds.put(key, keyInfo);
// Multicast the new id |-> message key mapping.
if (isJoined())
{
sendFeedState(keyInfo);
}
} // end of assignKeyId(EMessageKey, EFeedState)
/**
* Re-sends current state of all application message keys
* to the multicast group.
*/
private void feedStateRequest()
{
// Yes. Send McastKeyMessages filtering out the system
// messages.
mKeyIds.values()
.stream()
.filter(k -> k.keyId() >= 0)
.forEach(k -> sendFeedState(k));
} // end of feedStateRequest(InetSocketAddress)
/**
* Sends the latest message key feed state to joined
* multicast group.
* @param keyInfo contains message key, key identifier, and
* current feed state.
*/
private void sendFeedState(final KeyInfo keyInfo)
{
final McastKeyMessage.Builder builder =
McastKeyMessage.builder();
final EMessageKey key = keyInfo.key();
final McastKeyMessage message =
builder.messageClass(
(key.messageClass()).getCanonicalName())
.messageSubject(key.subject())
.multicastId(MCAST_ID)
.keyId(keyInfo.keyId())
.feedState(keyInfo.state())
.build();
final EMessageHeader header =
new EMessageHeader(
(SystemMessageType.MCAST_KEY).keyId(),
0,
0,
message);
// Post header to target address.
send(header, mTarget);
} // end of sendFeedState(KeyInfo)
} // end of class EMulticastPublisher
© 2015 - 2025 Weber Informatics LLC | Privacy Policy