All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.client.EMulticastPublisher Maven / Gradle / Ivy

There is a newer version: 7.6.0
Show newest version
//
// 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 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