net.sf.eBus.client.EMulticastSubscriber 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.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
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 net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.util.MultiKey2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for subscribing to notification
* messages from its multicast connection. This means it
* maintains a {@link EMultiPublishFeed multi-feed publisher} to
* eBus. The notification messages received from the multicast
* group are then posted to the multi-feed if the
* inbound message
*
* The multi-feed message keys are either fixed or dynamic. A
* fixed feed consists of either a subject list or a non-dynamic
* pattern. If a list, then only those subjects in that list are
* published to eBus from the multicast group. If a non-dynamic
* pattern, then only those subjects which match the query upon
* opening the feed are used. The problem here is that if there
* are no matching feeds on open then no messages will be
* forwarded from the multicast group to eBus.
*
*
* A dynamic feed uses a subject pattern. Only those
* notifications published on the feed which match that pattern
* are forwarded to eBus. If new subjects are added to eBus
* after opening the multi-feed which match the pattern, those
* new subjects will be forwarded by the multicast subscriber.
*
*
* @see EMulticastConnection
* @see EMulticastPublisher
*
* @author Charles W. Rapp
*/
public final class EMulticastSubscriber
extends EMulticastConnection
implements EPublisher
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(EMulticastSubscriber.class);
//-----------------------------------------------------------
// Locals.
//
/**
* Maps notification class to its multi-feed publisher.
*/
private final Map mFeeds;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new instance of EMulticastSubscriber.
*/
/* package */ EMulticastSubscriber(final MulticastConnection config)
{
super (config);
mFeeds = new ConcurrentHashMap<>();
} // end of EMulticastSubscriber(MulticastConnection)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Overrides.
//
/**
* Opens a multi-feed subscriber for each of the multicast
* message keys. Multi-feed subscriber type will be either
* list-based or query-based depending on the multicast
* message key configuration.
*/
@Override
protected void onOpen()
{
Class extends ENotificationMessage> mc;
String mcName;
EMultiPublishFeed.Builder builder;
EMultiPublishFeed feed;
sLogger.debug("{}: open EMultiPublishFeeds.", mName);
for (MulticastKey key : mMulticastKeys.values())
{
mc = key.messageClass();
mcName = mc.getCanonicalName();
mIsDynamic = (mIsDynamic || key.isDynamic());
builder = EMultiPublishFeed.builder();
sLogger.trace("{}: opening multi-feed {}.",
mName,
mcName);
// Fixed subject list or dynamic subject query?
if (key.feedType() == MultifeedType.LIST)
{
// Fixed subject list.
feed = builder.target(this)
.location(ClientLocation.REMOTE)
.messageClass(mc)
.subjects(key.subjectList())
.scope(FeedScope.LOCAL_ONLY)
.build();
}
// Dynamic subject query. Set the initial subject
// list to an empty list because the query is used
else
{
feed = builder.target(this)
.location(ClientLocation.REMOTE)
.messageClass(mc)
.query(key.subjectQuery())
.scope(FeedScope.LOCAL_ONLY)
.build();
}
mFeeds.put(mcName, feed);
}
} // end of onOpen()
/**
* When the multicast group is successfully joined, the
* configured notification feeds are advertised to eBus and
* a {@link McastSubscribeMessage} is multicast to the
* group publishers.
*/
@Override
protected void onConnect()
{
final EMessageHeader header =
new EMessageHeader(
(SystemMessageType.MCAST_SUBSCRIBE_KEY).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(McastSubscribeMessage.builder()).build());
sLogger.debug(
"{}: advertising EMultiPublishFeeds.", mName);
mFeeds.values().forEach(this::advertiseFeed);
// Multcast the McastSubscribeMessage to retrieve the
// current multicast publisher feed state.
send(header, mTarget);
} // end of onConnect()
/**
* Marks all published multi-feeds as down as a result of the
* multicast group disconnect.
*/
@Override
protected void onDisconnect()
{
final Collection feeds =
new ArrayList<>(mFeeds.values());
feeds.forEach(this::feedStateDown);
} // end of onDisconnect
/**
* Closes all multi-feed publishers and then discards them.
*/
@Override
protected void onClose()
{
final Collection feeds =
new ArrayList<>(mFeeds.values());
sLogger.debug("{}: closing EMultiPublishFeeds.", mName);
mFeeds.clear();
feeds.forEach(EMultiPublishFeed::close);
} // end of onClose()
/**
* Handles an inbound multicast message. If the message is a
* notification supported by this multicast subscriber and
* that feed's state is up, then the notification message is
* published on eBus. If the message is an
* {@link McastKeyMessage} which references a supported feed
* then the feed's state is updated as per the message
* contents. All other messages are ignored.
* @param msg inbound multicast message.
* @param source message source address.
*/
@Override
protected void onMessage(final EMessage msg,
final InetSocketAddress source)
{
final Class> msgClass = msg.getClass();
if (sLogger.isTraceEnabled())
{
sLogger.trace(
"{}: received {} {} message:\n{}",
mName,
msg.key(),
msg.messageType(),
msg);
}
else
{
sLogger.debug("{}: received {} {} message.",
mName,
msg.key(),
msg.messageType());
}
// Is this a notification?
if ((ENotificationMessage.class).isAssignableFrom(msgClass))
{
final EMultiPublishFeed feed =
mFeeds.get((msg.getClass()).getCanonicalName());
// Yes. Is this a supported message class?
// Is this a supported message subject?
if (feed != null && feed.isSubject(msg.subject))
{
// Yes in both casess.
feed.publish((ENotificationMessage) msg);
}
}
// Is this a publisher status update?
else if (McastKeyMessage.class.equals(msgClass))
{
final McastKeyMessage keyMsg =
(McastKeyMessage) msg;
final EMultiPublishFeed feed =
mFeeds.get(keyMsg.messageClass);
sLogger.trace(
"{}: checking if {} is a supported feed.",
mName,
keyMsg.messageClass);
// Yes. Is this a supported message class?
if (feed != null)
{
// Yes. Update the message key feed state.
onKeyUpdate(keyMsg, source);
}
}
} // end of onMessage(EMessage, InetSocketAddress)
//
// end of Abstract Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// EPublisher Interface Implementation.
//
/**
* Logs the change in a feed's publish state.
* @param feedState latest feed publish state.
* @param feed update applies to this feed.
*/
@Override
public void publishStatus(final EFeedState feedState,
final IEPublishFeed feed)
{
sLogger.debug("{}: setting feed {} to {}.",
mName,
feed.key(),
feedState);
} // end of publishStatus(EFeedState, IEPublishFeed)
//
// end of EPublisher Interface Implementation.
//-----------------------------------------------------------
/**
* Advertises the published multi-feed to eBus and sets the
* publish feed state to up.
* @param feed advertise this multi-feed.
*/
private void advertiseFeed(final EMultiPublishFeed feed)
{
sLogger.trace("{}: advertising {}, feed is up.",
mName,
feed.key());
feed.advertise();
feed.updateFeedState(EFeedState.UP);
} // end of advertiseFeed(EMultiPublishFeed)
/**
* Sets given multi-feed publish state to down.
* @param feed this multi-feed publish state is down.
*/
private void feedStateDown(final EMultiPublishFeed feed)
{
sLogger.trace("{}: {} feed is down.",
mName,
feed.key());
if (feed.isAdvertised())
{
feed.updateFeedState(EFeedState.DOWN);
}
} // end of feedStateDown()
/**
* Updates the message feed state as per the given
* multicast key message. If the message key does not
* appear in the supported key maps but is supported by this
* multicast subscriber, then a new message key is created
* and its supporting information is placed into the maps.
* Finally if the message keys's new multicast feed state
* has changed then corresponding multi-feed state is also
* updated.
* @param msg contains message key feed's latest state.
* @param source message source address.
*/
@SuppressWarnings({"java:S3776"})
private void onKeyUpdate(final McastKeyMessage msg,
final InetSocketAddress source)
{
final EFeedState feedState = msg.feedState;
final MultiKey2 mapKey =
new MultiKey2<>(msg.multicastId, msg.keyId);
final EMessageKey msgKey;
final KeyInfo keyInfo;
sLogger.trace(
"{}: updating map key {} message key {} to {}.",
mName,
mapKey,
msg.messageKey(),
feedState);
// Is this a new key?
if (mKeys.containsKey(mapKey))
{
// No. Use the existing key information.
msgKey = mKeys.get(mapKey);
keyInfo = mKeyIds.get(msgKey);
}
// This key is not known. Create a new key if-and-only-if
// this subject is supported by this subscriber.
else
{
keyInfo = createKeyInfo(msg, source);
// Was a new multicast key created?
if (keyInfo != null)
{
// Yes. Add the message key to the maps.
msgKey = keyInfo.key();
sLogger.trace(
"{}: adding new keys {} and {} to maps.",
mName,
keyInfo,
msgKey);
mKeys.put(mapKey, msgKey);
mKeyIds.put(msgKey, keyInfo);
}
// No and that means the message key is not supported
// by this multicast subscriber.
}
// Is there a key to update?
// Did the key's feed state change?
if (keyInfo == null)
{
// No.
sLogger.trace(
"{}: {} {} is an unsupported message key.",
mName,
mapKey,
msg.messageKey());
}
else if (keyInfo.update(feedState, source))
{
// Yes and yes. Update the matching multi-feed state.
final EMultiPublishFeed feed =
mFeeds.get(msg.messageClass);
final String subject = msg.messageSubject;
sLogger.trace("{}: {} {} is {}.",
mName,
source,
msg.messageKey(),
feedState);
// If this subject is *not* a part of the multi-feed
// publisher, then add it in first. This will happen
// if this multicast subscriber has a dynamic query
// matching subject.
if (!feed.isSubject(subject))
{
feed.addFeed(subject);
}
feed.updateFeedState(subject, keyInfo.state());
}
else if (sLogger.isTraceEnabled())
{
sLogger.trace("{}: {} {} {} feed state unchanged.",
mName,
source,
msg.messageKey(),
feedState);
}
} // end of onKeyUpdate(McastKeyMessage, InetSocketAddress)
/**
* Returns a new message key information object based on the
* given multicast key update message. Returns {@code null}
* if the updated message key is not supported by this
* multicast subscriber. This is the case if
* {@link McastKeyMessage#messageSubject} is not in a
* {@code EMultiPublishFeed}'s subject list or does not
* match a dynamic subject pattern.
* @param msg multicast message key update message.
* @param source message source.
* @return new multicast key information or {@code null}.
*/
private KeyInfo createKeyInfo(final McastKeyMessage msg,
final InetSocketAddress source)
{
final String subject = msg.messageSubject;
final MulticastKey mcastKey =
mMulticastKeys.get(msg.messageClass);
final EMultiPublishFeed feed =
mFeeds.get(msg.messageClass);
KeyInfo retval = null;
// Is this feed dynamic and does this subject match a
// dynamic pattern?
// Or is this one of the fixed subjects?
if ((mcastKey != null &&
mcastKey.matches(subject)) ||
(feed != null &&
feed.isSubject(subject)))
{
// Yes this is a supported subject.
final EMessageKey msgKey = msg.messageKey();
final MessageType mt =
(MessageType) DataType.findType(
msgKey.messageClass());
retval =
new KeyInfo(msgKey,
source,
msg.multicastId,
msg.keyId,
EFeedState.UNKNOWN,
new MessageReader(
msg.keyId, subject, mt));
}
return (retval);
} // end of createKeyInfo(McastKeyMessage, InetSocketAddress)
} // end of class EMulticastSubscriber