net.sf.eBus.client.EMulticastConnection 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 com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.MembershipKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import static net.sf.eBus.client.EAbstractConnection.MAX_MESSAGE_SIZE;
import static net.sf.eBus.client.EAbstractConnection.MESSAGE_HEADER_SIZE;
import static net.sf.eBus.client.EAbstractConnection.MESSAGE_SIZE_SIZE;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.MulticastMessage.MulticastState;
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;
import net.sf.eBus.config.EConfigure.McastNotifyConfig;
import net.sf.eBus.config.EConfigure.MulticastConnection;
import net.sf.eBus.config.EConfigure.MulticastRole;
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.ESystemMessage;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.UnknownMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AbstractAsyncDatagramSocket;
import net.sf.eBus.net.AsyncMulticastSocket;
import net.sf.eBus.net.AsyncMulticastSocket.MulticastBuilder;
import net.sf.eBus.net.DatagramBufferWriter;
import net.sf.eBus.net.DatagramListener;
import net.sf.eBus.util.HexDump;
import net.sf.eBus.util.LazyString;
import net.sf.eBus.util.MultiKey2;
import net.sf.eBus.util.logging.StatusReport;
import net.sf.eBus.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@code EMulticastConnection} is the API gateway to eBus
* multicast communication. This interface does not
* connect two or more eBus applications together in the same
* manner as {@link ERemoteApp}, exchanging notification and
* request advertisements with remote {@link FeedScope}. Instead
* {@code EMulticastConnections} are configured to either send
* or receive specific {@link ENotificationMessage} message keys
* via a multicast group.
*
* {@link EMulticastPublisher} uses a
* {@link EMultiSubscribeFeed} to receive remotely-scope
* notification messages matching the configured notification
* message keys. These message are then sent on the multicast
* group. {@link EMulticastSubscriber} receive these multicast
* notifications and, if the notification message key is one of
* the multicast keys, posts the inbound message to the
* {@link EMultiPublishFeed}.
*
*
* Both multicast publish and subscriber use
* {@link net.sf.eBus.config.EConfigure.McastNotifyConfig} to
* define the multi-feed key(s) used. Only those multi-feed
* notification feeds in the list will be send by the multicast
* publisher or received by the multicast subscriber. For more
* information on multi-feeds see {@link EMultiFeed},
* {@link EMultiPublishFeed}, and {@link EMultiSubscribeFeed}.
*
*
* Multicast publishers and subscribers coordinate message key
* feed state using the {@link McastSubscribeMessage} and
* {@link McastKeyMessage} system messages. A subscriber
* multicasts a {@code McastSubscribeMessage} which causes all
* publishers joined to the group to respond by multicasting
* their {@code McastKeyMessage}s. This informs the multicast
* subscriber as to what notifications are available and their
* current feed state. Multicast publishers post the multicast
* key state on demand, on start up, and on feed status update.
*
*
* Aside: since request/reply messages are exchanged directly
* between the requester and replier it makes no sense to
* multicast such messages.
*
* Opening a Multicast Connection
* A multicast connection is opened by first configuring the
* connection and then using that configuration to open the
* desired connection type. Multicast connection configuration is
* done using an
* {@code EConfigure.MulticastConnection} instance which is
* acquired using
* {@link net.sf.eBus.config.EConfigure#multicastBuilder()}. See
* {@link net.sf.eBus.config.EConfigure.MulticastConnection} for
* detailed explanation of how to configure a multicast
* connection.
*
* The following example shows how to configure and then open an
* eBus multicast connection.
*
* final EConfigure.MulticastBuilder builder = EConfigure.multicastBuilder();
final InetAddress group = InetAddress.getByName("230.0.0.0");
final NetworkInterface netIF = NetworkInterface.getByName("en8");
final String messageClass = com.stockreport.notification.LatestTrade.class.getCanonicalName();
final List<String> messageSubjects = com.google.common.collect.ImmutableList<>.of("ALPH", "BETA", "GMMA", "DLTA");
final EConfigure.McastNotifyConfig mcastNotification =
(EConfigure.notificationBuilder()).feedType(EConfigure.MultifeedType.LIST)
.messageClass(messageClass)
.subjectList(messageSubjects)
.build();
final List<MulticastKey> mcastFeeds = com.google.common.collect.ImmutableList<>.of(mcastNotification);
final EConfigure.MulticastConnection mcastConfig =
builder.name("McastConn_0")
.role(EConfigure.MulticastRole.PUBLISHER)
.group(group)
.targetPort(5000)
.networkInterface(netIF)
.bindPort(5001)
.protocolFamily(StandardProtocolFamily.INET)
.byteOrder(ByteOrder.LITTLE_ENDIAN)
.inputBufferSize(1_024)
.outputBufferSize(1_024)
.notifications(mcastFeeds)
.build();
EMulticastConnection.openConnection(mcastConfig);
* Monitoring Multicast Connection State
* If there is a need to monitor multicast connection state,
* then subscribe to {@link MulticastMessage#MESSAGE_KEY}. This
* can be done as follows:
* ESubsubscribeFeed.open(this,
MulticastMessage.MESSAGE_KEY,
FeedScope.LOCAL_ONLY,
null);
*
* @author Charles W. Rapp
*/
public abstract class EMulticastConnection
implements DatagramListener,
EObject
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* The unique identifier for this JVM.
*/
protected static final UUID MCAST_ID = UUID.randomUUID();
//-----------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(EMulticastConnection.class);
/**
* Existing multicast connections.
*/
private static final Map, EMulticastConnection> sConnections =
new HashMap<>();
/**
* Singleton responsible for publishing
* {@link ConnectionMessage} to subscribers.
*/
private static final ConnectionPublisher sConnPublisher;
// Class static initialization.
static
{
// Register the multicast connection status reporter.
(StatusReport.getsInstance()).register(
EMulticastConnection::reportMulticastStatus);
sConnPublisher = new ConnectionPublisher();
EFeed.register(sConnPublisher);
EFeed.startup(sConnPublisher);
} // end of class static initialization.
//-----------------------------------------------------------
// Locals.
//
/**
* Multicast connection configuration.
*/
protected final MulticastConnection mConfig;
/**
* Multicast connection name.
*/
protected final String mName;
/**
* Join this multicast group.
*/
protected final InetAddress mGroup;
/**
* Post messages to this multicast group and port.
*/
protected final InetSocketAddress mTarget;
/**
* eBus client reference to this object. Used to post tasks
* to the dispatcher thread.
*/
protected EClient mEClient;
/**
* Message key, subject pattern pair used to create the
* multi-feed publisher or subscriber feed. Maps the
* canonical message class name to the multicast key.
*/
protected final Map mMulticastKeys;
/**
* Maps a (JVM identifier, key identifier) pair to its
* associated message key. When a multicast
* packet is received, the JVM and key identifiers
* (found in message header) are used to look up the
* message key. In turn the message key used to decode the
* eBus message.
*/
protected final Map, EMessageKey> mKeys;
/**
* Maps message key to its associated information.
*/
protected final Map mKeyIds;
/**
* This instance creation timestamp.
*/
private final Date mCreated;
/**
* Multicast key used in {@link #sConnections} map.
*/
private final MultiKey2 mMultiKey;
/**
* {@code MessageWriter} is responsible for encoding outbound
* messages to the multicast group.
*/
private final MessageWriter mWriter;
/**
* Multicast group membership keys. There is one for each
* publisher source. If no sources are specified then this
* set will contain the single unsourced key.
*/
private final List mMcastKeys;
/**
* Set to {@code true} when joined to a multicast group.
*/
private boolean mIsJoined;
/**
* Set to {@code true} if at least one of the multicast
* notification keys is dynamic.
*/
protected boolean mIsDynamic;
/**
* Asynchronous multicast socket.
*/
private AsyncMulticastSocket mMcastSocket;
/**
* Count up the number of attempted multicast transmit
* attempts.
*/
private int mTransmitAttemptCount;
/**
* Number of successful multicast transmits.
*/
private int mTransmitSuccessCount;
/**
* Number of failed multicast transmits.
*/
private int mTransmitFailCount;
/**
* Number of datagram messages received.
*/
private int mReceiveCount;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new multicast connection instance based on the
* given configuration.
* @param config multicast connection configuration.
* @throws IllegalArgumentException
* if {@code config} contains an invalid notification
* message class.
*/
protected EMulticastConnection(final MulticastConnection config)
{
mConfig = config;
mName = config.name();
mGroup = config.group();
mTarget = config.groupAddress();
mCreated = new Date();
mMultiKey =
new MultiKey2<>(config.role(), config.group());
mKeys = new HashMap<>();
mKeyIds = new HashMap<>();
mWriter = new MessageWriter();
mMulticastKeys = loadKeys(config.notifications());
mIsJoined = false;
mIsDynamic = false;
mMcastKeys = new ArrayList<>();
mTransmitAttemptCount = 0;
mTransmitSuccessCount = 0;
mTransmitFailCount = 0;
mReceiveCount = 0;
} // end of EMulticastConnection(MulticastConnection)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Declarations.
//
/**
* Called when the multicast connection is opened
* but not yet joined to multicast group. Subclass should
* open multi-feeds at this time but not advertise or
* subscribe.
*/
protected abstract void onOpen();
/**
* Called when multicast group is successfully joined.
* Subclass should advertise or subscribe its multi-feeds.
*/
protected abstract void onConnect();
/**
* Called prior to disconnecting multicast group.
* Allows subclass to report updated feed state.
*/
protected abstract void onDisconnect();
/**
* Called when a message is received from the given source
* address. Note that the message may be either a
* notification or a system message.
* @param msg inbound eBus message.
* @param source inbound message source.
*/
protected abstract void onMessage(final EMessage msg,
final InetSocketAddress source);
/**
* Called when multicast connection is disconnected from the
* multicast group and closed. Subclass should close its
* multi-feeds at this time. Subclass should not
* attempt to send any messages since the multicast
* connection is now closed.
*/
protected abstract void onClose();
//
// end of Abstract Method Declarations.
//-----------------------------------------------------------
//-----------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Initializes system message decoders and then begins
* joining multicast group.
*/
@Override
public void startup()
{
sLogger.info("{}: starting multicast connection.", mName);
initializeReaders();
connect(mConfig);
} // end of startup()
/**
* Disconnects from multicast group and closes multicast
* socket.
*/
@Override
public void shutdown()
{
sLogger.info("{}: stopping multicast connection.", mName);
close();
} // end of shutdown()
//
// end of EObject Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// DatagramListener Interface Implementation.
//
/**
* Decodes the given input into an eBus message and forwards
* message to {@link #onMessage(EMessage, InetSocketAddress)}.
* Does nothing if this multicast connection is no longer
* joined to the multicast group or {@code buffer} contains
* a message not supported by this multicast connection. In
* either case {@code buffer} is emptied before returning.
*
* Do not call this method. This method is
* part of the {@link AsyncMulticastSocket} listener API and
* meant to be called in that context.
*
* @param buffer contains encoded eBus message.
* @param address {@code buffer} contents were transmitted
* from this source.
* @param socket input received by this datagram socket.
*/
@Override
public void handleInput(final ByteBuffer buffer,
final InetSocketAddress address,
final AbstractAsyncDatagramSocket socket)
{
final int limit = buffer.limit();
sLogger.trace(
"{}: received {} bytes from {}:{}, state={}.",
mName,
buffer.limit(),
address.getHostString(),
address.getPort(),
(mIsJoined ? "joined" : "not joined"));
// Are we still joined to the multicast group?
if (mIsJoined)
{
// Yes. Decode the inbound message and have the
// subclass handle it. Pass along the source address
// since that is needed for decoding.
processInput(buffer, address);
}
// Not joined to the multicast group.
// Clear the buffer by setting the position to its limit.
buffer.position(limit);
} // end of handleInput(...
/**
* Logs the multicast socket exception.
* @param t log this exception.
* @param socket error occurred on this multicast socket.
*/
@Override
public void handleError(final Throwable t,
final AbstractAsyncDatagramSocket socket)
{
sLogger.warn("{}: multicast socket error.", mName, t);
} // end of handleError(...)
//
// end of DatagramListener Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get Methods.
//
/**
* Returns unique multicast name.
* @return multicast name.
*/
@Override
public final String name()
{
return (mName);
} // end of name()
/**
* Returns {@code true} if this multicast connection is
* currently joined to its target multicast group and
* {@code false} if not joined.
* @return {@code true} if joined to multicast connection.
*/
public final boolean isJoined()
{
return (mIsJoined);
} // end of isJoined()
//
// end of Get Methods.
//-----------------------------------------------------------
/**
* Returns a multicast connection according to the given
* multicast connection configuration. If the multicast
* role is {@link MulticastRole#PUBLISHER} then creates a
* {@link EMulticastPublisher} instance; otherwise creates
* {@link EMulticastSubscriber}. The actual multicast group
* join is performed asynchronously. It recommended that
* an application subscribe to the message key
* {@code net.sf.eBus.client.MulticastMessage:/eBus/multicast}
* (see {@link MulticastMessage#MESSAGE_KEY}) in order to
* receive updates on multicast join state.
* @param config multicast connection configuration.
* @return publisher or subscriber multicast connection.
* @throws NullPointerException
* if {@code config} is {@code null}.
* @throws IllegalStateException
* if there is already a multicast connection for the given
* role and group.
*/
public static EMulticastConnection openConnection(final MulticastConnection config)
{
Objects.requireNonNull(config, "config is null");
final MulticastRole role = config.role();
final InetAddress group = config.group();
final MultiKey2 key =
new MultiKey2<>(role, group);
final EMulticastConnection retval;
if (sConnections.containsKey(key))
{
throw (
new IllegalStateException(
String.format(
"already connected to %s as %s",
group.getHostAddress(),
role)));
}
sLogger.debug(
"Opening multicast connection {} to {} (net I/F {}) as {}.",
config.name(),
config.groupAddress(),
config.networkInterface(),
role);
if (config.role() == MulticastRole.PUBLISHER)
{
retval = new EMulticastPublisher(config);
}
else
{
retval = new EMulticastSubscriber(config);
}
sConnections.put(key, retval);
// Multicast connections are remote clients.
retval.mEClient =
EClient.findOrCreateClient(
retval, ClientLocation.REMOTE);
(retval.mEClient).dispatch(retval::startup);
return (retval);
} // end of openConnection(MulticastConnection)
/**
* Disconnects from the multicast group, closes the
* datagram socket, and removes this multicast connection
* from the connections map. This action permanently close
* this multicast connection. If the connection is needed
* again then
* {@link #openConnection(net.sf.eBus.config.EConfigure.MulticastConnection)}
* must be called again to re-create this multicast
* connection.
*
* @see #openConnection(net.sf.eBus.config.EConfigure.MulticastConnection)
*/
public final void close()
{
final AsyncMulticastSocket mcastSocket = mMcastSocket;
sLogger.debug(
"Closing multicast connection {} to {} (net I/F {}) as {}.",
mName,
mTarget,
mConfig.networkInterface(),
mConfig.role());
// Is this multicast connection open?
if (mcastSocket != null)
{
// Yes. Is it joined to any groups?
if (mIsJoined)
{
final List keys =
ImmutableList.copyOf(mMcastKeys);
// Yes. Give subclass chance to clean up before
// dropping the membership.
onDisconnect();
// Drop the group memberships.
mMcastKeys.clear();
keys.forEach(MembershipKey::drop);
mIsJoined = false;
}
mcastSocket.closeNow();
mMcastSocket = null;
sConnPublisher.publish(
mName, mGroup, MulticastState.DISCONNECTED);
// Give subclass a chance now to clean up.
onClose();
}
sConnections.remove(mMultiKey);
} // end of doClose()
/**
* Posts eBus header and message to the joined multicast
* group.
* @param header send this eBus header and message.
* @param target send encoded message to this address. May
* be either a multicast group and port and a unicast
* datagram address and port.
* @throws IllegalStateException
* if not currently joined to multicast group.
*/
protected void send(final EMessageHeader header,
final InetSocketAddress target)
{
if (mMcastSocket == null)
{
throw (
new IllegalStateException(
"multicast socket closed"));
}
if (!mIsJoined)
{
throw (
new IllegalStateException(
"not joined to multicast group"));
}
if (sLogger.isTraceEnabled())
{
sLogger.trace(
"{}: sending message to {}:{}:\n{}",
mName,
target.getHostString(),
target.getPort(),
header.message());
}
else
{
sLogger.debug(
"{}: sending {} message to {}:{}.",
mName,
header.messageKey(),
target.getHostString(),
target.getPort());
}
try
{
++mTransmitAttemptCount;
mWriter.post(header, target);
mMcastSocket.send(mWriter);
++mTransmitSuccessCount;
}
catch (BufferOverflowException |
IOException jex)
{
sLogger.warn(
"{}: failed to send {} to {}.",
mName,
(header.messageClass()).getCanonicalName(),
target,
jex);
++mTransmitFailCount;
}
} // end of send(EMessageHeader)
/**
* Opens a multicast connect as per
* {@link EConfigure#multicastConnections() }.
* @param config contains multicast connection configuration.
*/
/* package */ static void configure(final EConfigure config)
{
final Collection conns =
(config.multicastConnections()).values();
conns.forEach(EMulticastConnection::openConnection);
} // end of configure(EConfigure)
/**
* Converts the multicast key configurations to
* {@link MulticastKey} instances.
* @param config contains multicast key configuration.
* @return multicast key set.
* @throws IllegalArgumentException
* if {@code config} contains an invalid notification
* message class.
*/
@SuppressWarnings ("unchecked")
private static Map loadKeys(final List config)
{
String className;
final Map retval = new HashMap<>();
for (McastNotifyConfig mnc : config)
{
className = mnc.messageClass();
try
{
retval.put(className, new MulticastKey(mnc));
}
catch (ClassNotFoundException classex)
{
throw (
new IllegalArgumentException(
className + " is not a known class",
classex));
}
catch (ClassCastException castex)
{
throw (
new IllegalArgumentException(
className + " is not a notification message class",
castex));
}
}
return (retval);
} // end of loadKeys(List<>)
/**
* Initializes {@link #mKeys} and {@link #mKeyIds} maps to
* the multicast system messages. Application notification
* messages are added only by {@code EMulticastSubscriber}
* when {@code McastKeyMessage} is received.
*/
private void initializeReaders()
{
final SystemMessageType[] types =
SystemMessageType.values();
final int size = types.length;
int keyId;
int index;
Class extends EMessage> mc;
MessageType mt;
MultiKey2 mapKey;
EMessageKey msgKey;
sLogger.debug("{}: initializing message readers.", mName);
// Add multicast system message input readers to map.
for (index = 0; index < size; ++index)
{
if (types[index].isMulticast())
{
keyId = types[index].keyId();
mc = types[index].messageClass();
mt =
(MessageType) DataType.findType(mc);
mapKey = new MultiKey2<>(null, keyId);
msgKey =
new EMessageKey(
mc, ESystemMessage.SYSTEM_SUBJECT);
mKeys.put(mapKey, msgKey);
mKeyIds.put(msgKey,
new KeyInfo(
msgKey,
null,
MCAST_ID,
keyId,
// System message feeds are always up.
EFeedState.UP,
new MessageReader(
keyId,
ESystemMessage.SYSTEM_SUBJECT,
mt)));
}
}
} // end of initializeReaders()
/**
* Asynchronously performs multicast group join. Creates
* {@link AsyncMulticastSocket}, configured according to the
* given multicast connection configuration. First opens the
* multicast datagram channel and performs role-specific
* work. Then joins the multicast group or groups depending
* on whether source addresses where provided. Role-specific
* post-join actions are then taken.
*
* After successfully joining a {@link MulticastMessage} is
* published to {@link MulticastMessage#MESSAGE_KEY} with
* a multicast connection state set to
* {@code JOINED}.
*
* @param config multicast connection configuration.
*/
private void connect(final MulticastConnection config)
{
final InetSocketAddress bindAddr =
new InetSocketAddress(
config.bindAddress(), config.bindPort());
final ProtocolFamily family = config.protocolFamily();
final NetworkInterface netIf = config.networkInterface();
final MulticastBuilder builder =
AsyncMulticastSocket.builder();
sLogger.debug(
"{}: creating multicast socket:\n{}",
mName,
config);
mMcastSocket =
builder.byteOrder(config.byteOrder())
.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.selector(config.selector())
.listener(this)
.build();
try
{
final InetAddress group = config.group();
final List sources = config.sources();
sLogger.info(
"{}: connecting to multicast group {} on network I/F {}.",
mName,
group.getHostName(),
netIf.getDisplayName());
mMcastSocket.open(bindAddr, family, netIf);
// Successfully opened multicast socket.
onOpen();
sLogger.info(
"{}: joining multicast group {} on network I/F {}.",
mName,
group.getHostName(),
netIf.getDisplayName());
// Are there any sources for this multicast
// subscriber?
if (sources == null || sources.isEmpty())
{
// No, join the multicast group without a source.
mMcastKeys.add(mMcastSocket.join(group, netIf));
}
// Yes, there are sources. Create a membership key
// for each source.
else
{
for (InetAddress source : sources)
{
mMcastKeys.add(
mMcastSocket.join(group, netIf, source));
}
}
mIsJoined = true;
sLogger.debug(
"{}: joined to multicast group {} on network I/F {}.",
mName,
group.getHostName(),
netIf.getDisplayName());
sConnPublisher.publish(
mName, mGroup, MulticastState.JOINED);
// Successfully joined multicast group.
onConnect();
}
catch (IOException |
UnsupportedOperationException |
IllegalArgumentException jex)
{
sLogger.warn(
"{}: failed to open multicast socket (bind address={}, family={}, net I/F={})",
config.name(),
bindAddr,
family,
netIf.getDisplayName(),
jex);
}
} // end of connect(MulticastConnection)
/**
* Decodes eBus message contained in {@code buffer} and
* forwards that message to role-specific method
* {@link #onMessage(EMessage, InetSocketAddress)}.
* @param buffer buffer containing eBus message.
* @param source message source address.
*/
private void processInput(final ByteBuffer buffer,
final InetSocketAddress source)
{
if (sLogger.isTraceEnabled())
{
final int rem = buffer.remaining();
final int pos = buffer.position();
final int ms = buffer.getInt(pos);
final byte[] data = new byte[rem];
buffer.mark();
buffer.get(data);
buffer.reset();
sLogger.trace(
"{}:{}: {} bytes available (start={}, msg size={}):\n{}",
source.getHostString(),
source.getPort(),
rem,
pos,
ms,
new LazyString(
() -> HexDump.dump(data, 0, rem, " ")));
}
else
{
sLogger.debug("{}: {} bytes available.",
source,
buffer.remaining());
}
final int messageSize = buffer.getInt();
// The message size must be at least big enough for
// the message header or is within the maximum
// allowed message size.
if (messageSize < MESSAGE_HEADER_SIZE ||
messageSize > MAX_MESSAGE_SIZE)
{
sLogger.warn("{}: invalid message size ({} bytes).",
mName,
messageSize);
}
else
{
decode(buffer, source);
}
} // end of processInput(ByteBuffer, InetSocketAddress)
/**
* Decodes eBus message contained in {@code buffer} and then
* forwards decoded message to abstract method
* {@link #onMessage(EMessage, InetSocketAddress)}. Does
* nothing if {@code buffer} contains a message that is not
* supported by this multicast connection.
* @param buffer contains latest eBus message.
* @param source message posted from this network location.
*/
private void decode(final ByteBuffer buffer,
final InetSocketAddress source)
{
final UUID mcastId =
new UUID(buffer.getLong(), buffer.getLong());
final int keyId = buffer.getInt();
// If the keyId is < 0 that means this is a system
// message. In that case set the source to null.
final MultiKey2 infoKey =
new MultiKey2<>((keyId < 0 ? null : mcastId), keyId);
final EMessageKey msgKey = mKeys.get(infoKey);
final KeyInfo keyInfo = mKeyIds.get(msgKey);
sLogger.trace(
"{}: info key {} -> message key {}, keyInfo {}.",
mName,
infoKey,
msgKey,
keyInfo);
// Is this a supported message type?
if (keyInfo != null)
{
// Yes. Decode the inbound message and pass on to the
// publisher/subscriber.
try
{
final EMessageHeader header =
(keyInfo.reader()).extractMessage(
buffer, source);
++mReceiveCount;
onMessage(header.message(), source);
}
catch (BufferUnderflowException |
InvalidMessageException |
UnknownMessageException jex)
{
sLogger.warn("{}: failed to decode {}.",
mName,
msgKey,
jex);
}
}
} // end of decode(ByteBuffer, InetSocketAddress)
/**
* Appends the status of all multicast connections to status
* report.
* @param report append multicast connection status to
* this report.
*/
private static void reportMulticastStatus(final PrintWriter report)
{
final int count = sConnections.size();
report.print("Multicast connections: ");
if (count == 0)
{
report.println("no multicast connections.");
}
else
{
final Collection connections =
new ArrayList<>(sConnections.values());
int index = 0;
report.format(
"there %s %,d multicast %s.%n",
(count == 1 ? "is" : "are"),
count,
(count == 1 ? "connection" : "connections"));
for (EMulticastConnection conn : connections)
{
conn.reportStatus(report, index);
report.println();
++index;
}
}
} // end of reportMulticastStatus(PrintWriter)
/**
* Appends this multicast connection's status to the status
* report.
* @param report append status to this report.
* @param index connection map index.
*/
@SuppressWarnings({"java:S3398"})
private void reportStatus(final PrintWriter report,
final int index)
{
int i = 0;
report.format(" [%,d] address: %s%n", index, mTarget)
.format(
" created on %1$tY-%1$tm-%1$td @ %1$tH:%1$tM:%1$tS.%1$tL%n",
mCreated)
.format(" joined: %b%n", mIsJoined)
.format("attempted xmits: %,d%n",
mTransmitAttemptCount)
.format("completed xmits: %,d%n",
mTransmitSuccessCount)
.format(" failed xmits: %,d%n",
mTransmitFailCount)
.format(" received: %,d%n", mReceiveCount)
.format(" subscriptions:%n");
for (KeyInfo info : mKeyIds.values())
{
report.format(" [%2d] %s%n", i, info);
++i;
}
} // end of reportStatus(PrintWriter, int)
//---------------------------------------------------------------
// Inner classes.
//
/**
* Contains multi-feed notification key. The difference
* between this class and {@link McastNotifyConfig} is that
* the message class is stored here as a {@code Class}
* instance while {@code McastNotifyConfig} stores it as
* text. Also, this class provides the
* {@link #matches(String)} method.
*/
protected static final class MulticastKey
implements Comparable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Either a list or query notification multi-feed.
*/
private final MultifeedType mType;
/**
* Notification message class used to create a
* {@link EMessageKey}.
*/
private final Class extends ENotificationMessage> mMessageClass;
/**
* Multi-feed subject list.
*/
private final List mSubjectList;
/**
* Pattern used to match patterns.
*/
private final Pattern mSubjectQuery;
/**
* Set to {@code true} if this is a dynamic,query-based
* multi-feed.
*/
private final boolean mIsDynamic;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a multicast multi-feed keys based on given
* configuration.
* @param config multicast multi-feed configuration.
* @throws ClassNotFoundException
* if {@code config} contains an unknown message class.
* @throws ClassCastException
* if {@code config} contains a non-notification message
* class.
*/
@SuppressWarnings ("unchecked")
private MulticastKey(final McastNotifyConfig config)
throws ClassNotFoundException
{
mType = config.feedType();
mMessageClass =
(Class extends ENotificationMessage>)
Class.forName(config.messageClass());
mSubjectList = config.subjectList();
mSubjectQuery = config.subjectQuery();
mIsDynamic = config.isDynamic();
} // end of MulticastKey(...)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Comparable Interface Implementation.
//
/**
* Returns an integer value < 0, zero, or > 0
* if {@code this MulticastKey} is <, equal to, or
* > than the given key. Comparison is based on the
* message key class name only.
* @param o comparison multicast key.
* @return integer value.
*/
@Override
public int compareTo(final MulticastKey o)
{
final String name0 =
mMessageClass.getCanonicalName();
final String name1 =
(o.mMessageClass).getCanonicalName();
return (name0.compareTo(name1));
} // end of compareTo(MulticastKey)
//
// end of Comparable Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns text containing this multicast key's values.
* @return multicast key as text.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append("[type=").append(mType)
.append(", class=")
.append(mMessageClass.getCanonicalName());
if (mType == MultifeedType.QUERY)
{
retval.append(", query=")
.append(mSubjectQuery.pattern())
.append(", is dynamic=")
.append(mIsDynamic);
}
else
{
String sep = "";
retval.append(", list={");
for (String subject : mSubjectList)
{
retval.append(sep)
.append("\"")
.append(subject)
.append("\"");
sep = ", ";
}
retval.append("}");
}
return (retval.append("]").toString());
} // end of toString()
/**
* Returns {@code true} if this multicast key is equal
* to the given object. Equality is based on message
* class name only.
* @param o check for equality with this object.
* @return {@code true} if {@code this MulticastKey}
* equals {@code o}.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof MulticastKey)
{
final MulticastKey key = (MulticastKey) o;
retcode =
mMessageClass.equals(key.mMessageClass);
}
return (retcode);
} // end of equals(Object)
/**
* Returns hash code based on the message class.
* @return message class hash code.
*/
@Override
public int hashCode()
{
return (mMessageClass.hashCode());
} // end of hashCode()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns multi-feed type.
* @return multi-feed type.
*/
public MultifeedType feedType()
{
return (mType);
} // end of feedType()
/**
* Returns notification message class.
* @return notification message class.
*/
public Class extends ENotificationMessage> messageClass()
{
return (mMessageClass);
} // end of messageClass()
/**
* Returns multi-feed subject list. If
* {@link #feedType()} is not {@link MultifeedType#LIST}
* then returns {@code null}.
* @return multi-feed subject list.
*
* @see #subjectQuery()
*/
public List subjectList()
{
return (mSubjectList);
} // end of subjectList()
/**
* Returns multi-feed subject query. If
* {@link #feedType()} is not {@link MultifeedType#QUERY}
* then returns {@code null}.
* @return multi-feed subject query.
*
* @see #subjectList()
* @see #isDynamic()
*/
public Pattern subjectQuery()
{
return (mSubjectQuery);
} // end of subjectQuery()
/**
* Returns {@code true} if this multi-feed is a dynamic
* query; otherwise returns {@code false}.
* @return {@code true} if multi-feed is dynamic query.
*
* @see #subjectQuery()
*/
public boolean isDynamic()
{
return (mIsDynamic);
} // end of isDynamic()
//
// end of Get Methods.
//-------------------------------------------------------
/**
* Returns {@code true} if this notification multi-feed
* is:
*
* -
* a {@link MultifeedType#QUERY} and
*
* -
* this feed is dynamic and
*
* -
* {@code subject} matches
* {@code this.subjectQuery()}
*
*
* Otherwise returns {@code false}.
* @param subject check if this message subject matches
* the subject query.
* @return {@code true} if {@code subject} matches this
* multicast key.
*/
public boolean matches(final String subject)
{
return (mIsDynamic &&
mType == MultifeedType.QUERY &&
mSubjectQuery.matches(subject));
} // end of matches(EMessageKey)
} // end of class MulticastKey
/**
* Contains a message keys's unique integer identifier,
* source address and its current feed state.
*/
protected static final class KeyInfo
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Information is for this message key.
*/
private final EMessageKey mKey;
/**
* Source address associated with the message key. Will
* be {@code null} if message key is a system message.
*/
private final InetSocketAddress mSource;
/**
* Message key identifier applies to this JVM.
*/
private final UUID mMcastId;
/**
* Unique message key identifier.
*/
private final int mKeyId;
/**
* Calculate the hash code once.
*/
private final int mHashCode;
/**
* Associated message decoder. Set for inbound messages
* only.
*/
private final MessageReader mReader;
/**
* Multicast publishers currently feeding this message
* key. Used by multicast subscriber to keep track of
* a message key feed state. As long as there is one
* multicast publisher with an up feed state, then
* {@link #mFeedState} is set to up.
*
* This data member is not used by
* {@code EMulticastPublisher}.
*
*/
private final List mPublishers;
/**
* Current message feed state. Initialized to
* {@code UNKNOWN}.
*/
private EFeedState mFeedState;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a multicast key information instance for the
* given message key, associated source address, unique
* message key identifier, and feed state.
* @param key multicast notification message key.
* @param source publisher source address.
* @param mcastId message key info is from this JVM.
* @param id {@code key}'s unique integer identifier.
* @param state current notification feed state.
*/
protected KeyInfo(final EMessageKey key,
final InetSocketAddress source,
final UUID mcastId,
final int id,
final EFeedState state)
{
this (key, source, mcastId, id, state, null);
} // end of KeyInfo(...)
/**
* Creates a multicast key information instance for the
* given message key, associated source address, unique
* message key identifier, and feed state.
* @param key multicast notification message key.
* @param source publisher source address.
* @param mcastId message key info is from this JVM.
* @param id {@code key}'s unique integer identifier.
* @param state current notification feed state.
* @param reader inbound message decoder.
*/
protected KeyInfo(final EMessageKey key,
final InetSocketAddress source,
final UUID mcastId,
final int id,
final EFeedState state,
final MessageReader reader)
{
mKey = key;
mSource = source;
mMcastId = mcastId;
mKeyId = id;
mHashCode = Objects.hash(source, mcastId, id);
mReader = reader;
mPublishers = new ArrayList<>();
mFeedState = state;
} // end of KeyInfo(...)
/**
* Creates a temporary key info instance used to report
* that the message key feed is down on the multicast
* group.
* @param keyInfo copy information from this key.
* @param state copy key state.
*/
protected KeyInfo(final KeyInfo keyInfo,
final EFeedState state)
{
mKey = keyInfo.mKey;
mSource = keyInfo.mSource;
mMcastId = keyInfo.mMcastId;
mKeyId = keyInfo.mKeyId;
mHashCode = keyInfo.mHashCode;
mReader = keyInfo.mReader;
mPublishers = null;
mFeedState = state;
} // end of KeyInfo(KeyInfo, EFeedState)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns text describing this multicast notification
* message key.
* @return {@code KeyInfo} described as text.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
return (retval.append("[key=").append(mKey)
.append(", source=")
.append(mSource == null ? "" : mSource)
.append(", id=").append(mKeyId)
.append(", state=").append(mFeedState)
.append("]")
.toString());
} // end of toString()
/**
* Returns {@code true} if {@code o} is a
* non-{@code null KeyInfo} instance with equal source
* address, JVM and key identifiers; otherwise returns
* {@code false}.
* @param o comparison object.
* @return {@code true} if the {@code this KeyInfo}
* instance equals {@code o}.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof KeyInfo)
{
final KeyInfo info = (KeyInfo) o;
retcode =
(Objects.equals(mSource, info.mSource) &&
Objects.equals(mMcastId, info.mMcastId) &&
mKeyId == info.mKeyId);
}
return (retcode);
} // end of equals(Object)
/**
* Returns hash code based on source address and unique
* key identifier.
* @return {@code KeyInfo} instance hash code.
*/
@Override
public int hashCode()
{
return (mHashCode);
} // end of hashCode()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns notification message key.
* @return notification message key.
*/
public EMessageKey key()
{
return (mKey);
} // end of key()
/**
* Returns message source address. If message key is for
* a system message then returns {@code null}.
* @return message source address.
*/
public InetSocketAddress source()
{
return (mSource);
} // end of source()
/**
* Returns unique multicast identifier.
* @return multicast identifier.
*/
public UUID mcastId()
{
return (mMcastId);
} // end of mcastId()
/**
* Returns integer identifier for message key. This
* identifier is unique for the given source address.
* It is possible that difference sources will assign the
* same identifier for the same message key.
* @return message key identifier.
*/
public int keyId()
{
return (mKeyId);
} // end of keyId()
/**
* Returns the current feed state for message key.
* @return message key feed state.
*/
public EFeedState state()
{
return (mFeedState);
} // end of state()
/**
* Returns the inbound message decoder. Returns
* {@code null} if this {@code KeyInfo} instance is for
* outbound messages only.
* @return inbound message decoder.
*/
public MessageReader reader()
{
return (mReader);
} // end of reader()
//
// end of Get Methods.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Updates the message key feed state to the given value.
* @param state latest message key feed state.
*/
public void state(final EFeedState state)
{
mFeedState = state;
} // end of state(EFeedState)
/**
* Updates publishers list by adding source address if
* {@code state} is up and removing address if down.
* Returns {@code true} if this changes the multicast
* feed state and {@code false} if feed state remains
* unchanged.
* @param state multicast source feed state.
* @param source multicast source.
* @return {@code true} if feed state has changed.
*/
public boolean update(final EFeedState state,
final InetSocketAddress source)
{
final EFeedState initialState = mFeedState;
// Add or remove publisher?
// Add only if source is not already a listed
// publisher.
if (state == EFeedState.UP &&
!mPublishers.contains(source))
{
// Add.
mPublishers.add(source);
}
// Is the feed state down or unknown?
else if (state != EFeedState.UP)
{
// Remove.
mPublishers.remove(source);
}
mFeedState = (mPublishers.isEmpty() ?
EFeedState.DOWN :
EFeedState.UP);
sLogger.trace("{}: {} {}, # publishers is {}.",
mKey,
(state == EFeedState.UP ?
"added" :
"removed"),
source,
mPublishers.size());
return (mFeedState != initialState);
} // end of update(EFeedState, InetSocketAddress)
//
// end of Set Methods.
//-------------------------------------------------------
} // end of class KeyInfo
/**
* This class is responsible for decoding the eBus header and
* message from an {@link AsyncMulticastSocket}'s underlying
* inbound buffer.
*/
protected static final class MessageReader
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* A unique message key identifier received from the
* remote eBus application.
*/
private final int mKeyId;
/**
* The message key subject. Needed to construct inbound
* messages.
*/
private final String mSubject;
/**
* The message type for deserializing the message class.
*/
private final MessageType mMessageType;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new multicast message reader for the given
* message key identifier, notification subject, and
* message data type.
* @param keyId associated message key identifier.
* @param subject associated notification subject.
* @param mt message data type containing decoder.
*/
protected MessageReader(final int keyId,
final String subject,
final MessageType mt)
{
mKeyId = keyId;
mSubject = subject;
mMessageType = mt;
} // end of MessageReader(int, String, MessageType)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the message key identifier.
* @return message key identifier.
*/
public int keyId()
{
return (mKeyId);
} // end of keyId()
public Class> messageClass()
{
return (mMessageType.dataClass());
} // end of messageClass()
/**
* Returns message key subject.
* @return message key subject.
*/
public String subject()
{
return (mSubject);
} // end of subject()
//
// end of Get Methods.
//-------------------------------------------------------
/**
* Returns the message header extracted from the given
* buffer starting at the buffer's current position.
* Note that the message size and message key identifier
* have already been extracted from {@code buffer}.
* @param buffer extract the message from this buffer.
* @param source data came from this source address.
* @return the extracted message header and message.
* @throws BufferUnderflowException
* if {@code buffer} contains an incomplete message.
* @throws UnknownMessageException
* if the de-serialized class identifier references an
* unknown message class.
* @throws InvalidMessageException
* if the message construction fails.
*/
private EMessageHeader extractMessage(final ByteBuffer buffer,
final InetSocketAddress source)
{
// Note: JVM and class identifiers already extracted
// by EMulticastConnect.decode().
final int fromFeedId = buffer.getInt();
final int toFeedId = buffer.getInt();
// Need to pass in the message subject explicitly
// because it is not longer serialized. Rather the
// subject is inferred from the message key
// identifier.
final EMessage msg =
(EMessage)
mMessageType.deserialize(mSubject, buffer);
return (
new EMessageHeader(
mKeyId, fromFeedId, toFeedId, source, msg));
} // end of extractMessage(ByteBuffer, InetSocketAddress)
} // end of class MessageReader
/**
* This class is responsible for serializing eBus message
* header and message directly to the underlying
* {@link AsyncMulticastSocket} outbound buffer.
*/
private static final class MessageWriter
implements DatagramBufferWriter
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Post outbound messages to this multicast group and
* port.
*/
private InetSocketAddress mTarget;
/**
* Store next outbound message here.
*/
private final Queue mTransmitQueue;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new outbound message writer for the parent
* multicast connection.
*/
private MessageWriter()
{
mTransmitQueue = new ConcurrentLinkedQueue<>();
} // end of MessageWriter()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// DatagramBufferWriter Interface Implementation.
//
/**
* Encodes the currently set eBus message to the given
* buffer.
* @param buffer place encoded eBus message into this
* location.
* @return target multicast group address.
*/
@Override
public SocketAddress fill(ByteBuffer buffer)
{
final EMessageHeader header = mTransmitQueue.poll();
final DataType msgType = header.dataType();
final int sizePosition = buffer.position();
final int size;
// Move past the message size bytes and fill in the
// rest of the header and message.
buffer.position(sizePosition + MESSAGE_SIZE_SIZE);
// Multicast messages start with the multicast
// identifier.
buffer.putLong(MCAST_ID.getMostSignificantBits())
.putLong(MCAST_ID.getLeastSignificantBits())
.putInt(header.classId())
.putInt(header.fromFeedId())
.putInt(header.toFeedId());
msgType.serialize(header.message(), null, buffer);
// Now write out the message size at the initial
// position.
size = (buffer.position() - sizePosition);
buffer.putInt(sizePosition, size);
return (mTarget);
} // end of fill(ByteBuffer)
//
// end of DatagramBufferWriter Interface Implementation.
//-------------------------------------------------------
/**
* Store this next outbound eBus message header. It is
* expected that
* {@link AsyncMulticastSocket#send(DatagramBufferWriter)}
* will be called immediately after calling this method.
* @param header eBus message header.
* @param target send message to this target address.
*/
private void post(final EMessageHeader header,
final InetSocketAddress target)
{
mTransmitQueue.add(header);
mTarget = target;
} // end of post(EMessageHeader)
} // end of class MessageWriter
/**
* This singleton is responsible for locally publishing
* multicast connection state updates to subscribers. This
* class is necessary since multicast connections are a
* remote client by definition and {@link ConnectionMessage}
* has local scope.
*/
private static final class ConnectionPublisher
implements EPublisher
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Constants.
//
/**
* eBus object name is {@value}.
*/
public static final String OBJECT_NAME =
"MulticastConnectionPublisher";
//-------------------------------------------------------
// Locals.
//
/**
* The {@link ConnectionMessage} notification feed.
*/
private EPublishFeed mStateFeed;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* {@code private} default constructor to prevent
* instantiation outside {@code EMulticastConnection}.
*/
private ConnectionPublisher()
{}
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Returns {@link #OBJECT_NAME} as eBus object name.
* @return eBus object name.
*/
@Override
public String name()
{
return (OBJECT_NAME);
} // end of name()
/**
* Open {@link MulticastMessage} notification feed.
*/
@Override
public void startup()
{
final EPublishFeed.Builder builder =
EPublishFeed.builder();
// Open the connect state feed and put its
// advertisement in place.
mStateFeed =
builder.target(this)
.messageKey(MulticastMessage.MESSAGE_KEY)
.scope(FeedScope.LOCAL_ONLY)
.build();
mStateFeed.advertise();
mStateFeed.updateFeedState(EFeedState.UP);
} // end of startup()
//
// end of EObject Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// EPublisher Interface Implementation.
//
/**
* If {@code feedState} is {@link EFeedState#UP}, then
* sends the current connection state for each known
* multicast connection.
* @param feedState either up or down.
* @param feed the connection state feed.
*/
@Override
public void publishStatus(final EFeedState feedState,
final IEPublishFeed feed)
{
sLogger.debug("{} feed is {}.", feed.key(), feedState);
// Is the feed now up?
if (feedState == EFeedState.UP)
{
// Yes. Send the current feed state for each of
// the known remote eBus connections.
final MulticastMessage.Builder builder =
MulticastMessage.builder();
sConnections.values()
.forEach(conn ->
mStateFeed.publish(builder.subject(mStateFeed.messageSubject())
.connectionName(conn.mName)
.multicastGroup(conn.mGroup)
.state(conn.mIsJoined ?
MulticastState.JOINED :
MulticastState.DISCONNECTED)
.build()));
}
// The feed is down. Nothing to send.
} // end of publishStatus(EFeedState, IEPublishFeed)
//
// end of EPublisher Interface Implementation.
//-------------------------------------------------------
/**
* Publishes a {@link MulticastMessage} with the given
* information if the feed is up.
* @param name multicast connection name.
* @param group connection state change applies to this
* multicast group.
* @param state new multicast connection state.
*/
private void publish(final String name,
final InetAddress group,
final MulticastState state)
{
if (mStateFeed.isFeedUp())
{
final MulticastMessage.Builder builder =
MulticastMessage.builder();
mStateFeed.publish(
builder.subject(mStateFeed.messageSubject())
.connectionName(name)
.multicastGroup(group)
.state(state)
.build());
}
} // end of publish(...)
} // end of class ConnectionPublisher
} // end of class EMulticastConnection